import { common } from '@kit.AbilityKit'; import { CommonConstants } from '../common/constants/CommonConstants'; import { VideoItem } from '../viewmodel/VideoItem'; import { AppUtil, ArrayUtil, FileUtil, LogUtil, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils'; import MediaTable from '../common/util/MediaTable'; import PlaylistTable from '../common/util/PlaylistTable'; import { EventConstants } from '../common/constants/EventConstants'; import { emitter } from '@kit.BasicServicesKit'; import { taskpool } from '@kit.ArkTS'; import PermissionUtil from '../common/util/PermissionUtil' // 批量删除 @Component export struct DeleteComptent { @State isDeleteYuan: boolean = true @State isDeletePicture: boolean = true @State isDeleteLrc: boolean = true @State onlyRemoveFromPlaylist: boolean = false @State packName: string = '' @State isDeleting: boolean = false // 删除状态 @State deleteProgress: number = 0 // 删除进度 (0-100) @State currentDeleteFile: string = '' // 当前删除的文件名 onDeleteResult = (_result: boolean) => { } onCancel = () => { } @Prop selectedFiles: Array @Prop isPlaylistMode: boolean = false @Prop playlistId: string = '' @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR; context = this.getUIContext().getHostContext() as common.UIAbilityContext async aboutToAppear() { this.packName = AppUtil.getBundleName() this.isDeleteYuan = PreferencesUtil.getBooleanSync('isDeleteYuan', true); this.isDeletePicture = PreferencesUtil.getBooleanSync('isDeletePicture', true); this.isDeleteLrc = PreferencesUtil.getBooleanSync('isDeleteLrc', true) if (!this.isPlaylistMode) { this.onlyRemoveFromPlaylist = false } } private hasOnlyDirectories(): boolean { return this.selectedFiles.length > 0 && this.selectedFiles.every(item => item.type === CommonConstants.TYPE_IS_DIR) } build() { Column() { if (this.isDeleting) { // 删除进度界面 Column() { Text('正在删除文件').fontSize(18).fontColor(Color.Red).margin({ top: 20, bottom: 15 }) // 当前文件名显示 if (this.currentDeleteFile) { Text(`正在删除: ${this.currentDeleteFile}`) .fontSize(14) .fontColor($r('app.color.text_color')) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .width('90%') .textAlign(TextAlign.Start) .margin({ bottom: 15 }) } // 进度条 Column() { Row() { Text(`${Math.round(this.deleteProgress)}%`) .fontSize(12) .fontColor($r('app.color.text_color')) Blank() Text(`${Math.round(this.deleteProgress * this.selectedFiles.length / 100)}/${this.selectedFiles.length}`) .fontSize(12) .fontColor($r('app.color.text_color')) } .width('90%') .margin({ bottom: 8 }) Progress({ value: this.deleteProgress, total: 100, type: ProgressType.Linear }) .width('90%') .height(8) .color(this.themeColor) .backgroundColor('#E0E0E0') .borderRadius(4) } .width('100%') .alignItems(HorizontalAlign.Center) .margin({ bottom: 20 }) // 取消按钮 Button('取消删除') .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .padding(15) .width(120) .onClick(() => { this.onCancel() }) .backgroundColor($r('app.color.silvery')) .backgroundBlurStyle(BlurStyle.COMPONENT_THICK) .fontColor(Color.Black) } .backgroundColor($r('app.color.start_window_background')) .backgroundBlurStyle(BlurStyle.Regular) .padding(20) } else { // 原始删除确认界面 Column() { Text('温馨提醒').fontSize(20).margin({ top: 10, bottom: 10 }) Text('是否删除这些文件?').fontSize(16).margin({ top: 10, bottom: 10 }) Column() { // 只移除歌单选项(歌单场景) Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.music_note_list')) .fontSize(20) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .margin({ left: 15 }) Text("只移除歌单") .fontSize(16) .layoutWeight(1) .margin({ left: 10 }) Toggle({ type: ToggleType.Checkbox, isOn: this.onlyRemoveFromPlaylist }) .onChange((isOn: boolean) => { this.onlyRemoveFromPlaylist = isOn; if (isOn) { this.isDeleteYuan = false this.isDeletePicture = false this.isDeleteLrc = false } }) .margin({ right: 28 }) .selectedColor(this.themeColor) } } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.onlyRemoveFromPlaylist = !this.onlyRemoveFromPlaylist if (this.onlyRemoveFromPlaylist) { this.isDeleteYuan = false this.isDeletePicture = false this.isDeleteLrc = false } }) .width('100%') .padding(10) .margin({ left: 18 }) .visibility(this.isPlaylistMode ? Visibility.Visible : Visibility.None) // 删除源文件选项 Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.doc_text')) .fontSize(20) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .margin({ left: 15 }) Text("删除源文件") .fontSize(16) .layoutWeight(1) .margin({ left: 10 }) Toggle({ type: ToggleType.Checkbox, isOn: this.isDeleteYuan }) .onChange((isOn: boolean) => { this.isDeleteYuan = isOn; PreferencesUtil.put('isDeleteYuan', this.isDeleteYuan) }) .margin({ right: 28 }) .selectedColor(this.themeColor) } } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isDeleteYuan = !this.isDeleteYuan }) .width('100%') .padding(10) .margin({ left: 18 }) .visibility(this.onlyRemoveFromPlaylist ? Visibility.None : Visibility.Visible) // 删除封面选项 Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.picture')) .fontSize(20) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .margin({ left: 15 }) Text("删除封面文件") .fontSize(16) .layoutWeight(1) .margin({ left: 10 }) Toggle({ type: ToggleType.Checkbox, isOn: this.isDeletePicture }) .onChange((isOn: boolean) => { this.isDeletePicture = isOn; PreferencesUtil.put('isDeletePicture', this.isDeletePicture) }) .margin({ right: 28 }) .selectedColor(this.themeColor) } } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isDeletePicture = !this.isDeletePicture }) .width('100%') .padding(10) .margin({ left: 18 }) .visibility(this.onlyRemoveFromPlaylist ? Visibility.None : Visibility.Visible) // 删除歌词选项 Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.input_mode')) .fontSize(20) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .margin({ left: 15 }) Text("删除歌词文件") .fontSize(16) .layoutWeight(1) .margin({ left: 10 }) Toggle({ type: ToggleType.Checkbox, isOn: this.isDeleteLrc }) .onChange((isOn: boolean) => { this.isDeleteLrc = isOn; PreferencesUtil.put('isDeleteLrc', this.isDeleteLrc) }) .margin({ right: 28 }) .selectedColor(this.themeColor) } } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.isDeleteLrc = !this.isDeleteLrc }) .width('100%') .padding(10) .margin({ left: 18, bottom: 10 }) .visibility(this.onlyRemoveFromPlaylist ? Visibility.None : Visibility.Visible) } .visibility(this.hasOnlyDirectories() ? Visibility.None : Visibility.Visible) Flex({ justifyContent: FlexAlign.SpaceAround }) { Button($r('app.string.cancel')) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .padding(15) .width(120) .onClick(() => { this.onCancel() }) .backgroundColor($r('app.color.silvery')) .backgroundBlurStyle(BlurStyle.COMPONENT_THICK) .fontColor(Color.Black) Button($r('app.string.sure')) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .padding(15) .width(120) .onClick(() => { this.doDeleteTask() }) .backgroundColor($r('app.color.silvery')) .backgroundBlurStyle(BlurStyle.COMPONENT_THICK) .fontColor(Color.Red) }.margin({ bottom: 10 }) } .backgroundColor($r('app.color.start_window_background')) .backgroundBlurStyle(BlurStyle.Regular) } } } doDeleteTask(){ if (this.onlyRemoveFromPlaylist && StrUtil.isEmpty(this.playlistId)) { ToastUtil.showToast('当前歌单信息无效') return } // 设置删除状态 this.isDeleting = true; this.deleteProgress = 0; this.currentDeleteFile = ''; const totalFiles = this.selectedFiles.length; let currentFileIndex = 0; // 创建进度监控定时器 let progressTimer: number = setInterval(() => { // 基于时间模拟进度 if (currentFileIndex < totalFiles) { this.deleteProgress = (currentFileIndex / totalFiles) * 95; // 最高到95% this.currentDeleteFile = this.selectedFiles[currentFileIndex]?.name || ''; currentFileIndex++; } if (this.deleteProgress >= 95) { clearInterval(progressTimer); } }, 300); // 每300ms更新一次,模拟删除进度 const task = this.onlyRemoveFromPlaylist ? new taskpool.Task( removeSongsFromPlaylistOnly, JSON.stringify(this.selectedFiles), this.context, this.playlistId ) : new taskpool.Task( deleteMultipleFilesWithProgress, JSON.stringify(this.selectedFiles), this.isDeleteYuan, this.isDeletePicture, this.isDeleteLrc, this.context, this.packName ); taskpool.execute(task, taskpool.Priority.HIGH).then((result) => { clearInterval(progressTimer); this.deleteProgress = 100; this.currentDeleteFile = '删除完成'; setTimeout(() => { if(result){ this.onDeleteResult(true) }else { this.onDeleteResult(false) } },500) }).catch((error:Error) => { clearInterval(progressTimer); console.error('heanup DeleteComptent: delete failed:', (error as Error).message); this.isDeleting = false; }); } } // 仅从当前歌单移除歌曲 @Concurrent async function removeSongsFromPlaylistOnly( selectedFilesStr:string, context:Context, playlistId:string ) { const selectedFiles: VideoItem[] = JSON.parse(selectedFilesStr) if (!ArrayUtil.isNotEmpty(selectedFiles)) { return true } if (StrUtil.isEmpty(playlistId)) { LogUtil.error('heanup DeleteComptent', 'removeSongsFromPlaylistOnly: playlistId 为空') return false } const playlistTable: PlaylistTable = new PlaylistTable(context) let hasPlaylistChanges = false let hasError = false for (let i = 0; i < selectedFiles.length; i++) { const item = selectedFiles[i] if (!item.filePath) { continue } try { const removed = await playlistTable.removeSongFromPlaylist(playlistId, item.filePath) if (removed) { hasPlaylistChanges = true } else { hasError = true } } catch (error) { hasError = true LogUtil.error('heanup DeleteComptent', `removeSongsFromPlaylistOnly error: ${(error as Error).message}`) } await new Promise(resolve => setTimeout(resolve, 120)) } if (hasPlaylistChanges) { const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH } emitter.emit(eventRefresh, {}) emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH_SORT }, {}) } return !hasError } //多选删除文件(带进度显示的新版本) @Concurrent async function deleteMultipleFilesWithProgress( selectedFilesStr:string, isDeleteYuan:boolean, isDeletePicture:boolean, isDeleteLrc:boolean, context:Context, packName:string ) { const selectedFiles: VideoItem[] = JSON.parse(selectedFilesStr) if (ArrayUtil.isNotEmpty(selectedFiles)) { const table: MediaTable = new MediaTable(context) const playlistTable: PlaylistTable = new PlaylistTable(context) // 初始化数据库 await new Promise((resolve, reject) => { table.getRdbStore(context, (err:Error) => { err ? reject(err) : resolve(); }); }); let hasPlaylistChanges = false; // 标记是否有歌单变化 const totalFiles = selectedFiles.length; for (let i = 0; i < selectedFiles.length; i++) { const item = selectedFiles[i]; console.info('heanup DeleteComptent: deleting file ' + (i + 1) + '/' + totalFiles + ': ' + item.name); if (item.type === CommonConstants.TYPE_IS_DIR) { // 删除目录 await new Promise((resolve) => { table.deleteDataForParentPath(item.filePath, async () => { try { await FileUtil.rmdir(item.filePath); console.info('heanup DeleteComptent: directory deleted: ' + item.filePath); } catch (error) { console.error('heanup DeleteComptent: directory delete error: ' + (error as Error).message); } resolve(); }); }); } else { // 删除文件 await new Promise((resolve) => { table.deleteData(item, async () => { try { // 删除源文件 if(isDeleteYuan && item.filePath.toLowerCase().includes(packName)){ await FileUtil.unlink(item.filePath); console.info('heanup DeleteComptent: source file deleted: ' + item.filePath); } // 删除封面文件 if (isDeletePicture && item.pixelMapPath) { const picPath = FileUtil.getFilePath(item.pixelMapPath); if (FileUtil.accessSync(picPath)) { await FileUtil.unlink(picPath); console.info('heanup DeleteComptent: cover file deleted: ' + picPath); } } // 删除歌词文件 if(isDeleteLrc) { const lyricPath = item.filePath?.replace(/\.[^/.]+$/, ".lrc"); if (lyricPath && item.filePath.toLowerCase().includes(packName) && FileUtil.accessSync(lyricPath)) { await FileUtil.unlink(lyricPath); console.info('heanup DeleteComptent: lyric file deleted: ' + lyricPath); } } } catch (error) { console.error('heanup DeleteComptent: file delete error: ' + (error as Error).message); } resolve(); }); }); // 从歌单中移除歌曲 LogUtil.info('heanup DeleteComptent', `准备从歌单中移除歌曲: ${item.name}, filePath: ${item.filePath}`); if (item.filePath) { try { const affectedPlaylists = await playlistTable.removeSongFromAllPlaylists(item.filePath); LogUtil.info('heanup DeleteComptent', `removeSongFromAllPlaylists 返回了 ${affectedPlaylists.length} 个受影响的歌单`); if (affectedPlaylists.length > 0) { hasPlaylistChanges = true; LogUtil.info('heanup DeleteComptent', `歌曲已从 ${affectedPlaylists.length} 个歌单中移除: ${item.name}`); } else { LogUtil.info('heanup DeleteComptent', `歌曲不在任何歌单中: ${item.name}`); } } catch (error) { LogUtil.error('heanup DeleteComptent', `从歌单移除歌曲失败: ${(error as Error).message}`); } } else { LogUtil.warn('heanup DeleteComptent', `歌曲 ${item.name} 的 filePath 为空`); } } // 短暂延迟,让用户能看到进度变化 await new Promise(resolve => setTimeout(resolve, 200)); } // 如果有歌单变化,发送刷新事件 if (hasPlaylistChanges) { const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }; emitter.emit(eventRefresh, {}); LogUtil.info('heanup DeleteComptent', '已发送歌单刷新事件,更新歌单数量'); } } return true } // 保留原始删除函数作为备用 @Concurrent async function deleteMultipleFiles( selectedFilesStr:string, isDeleteYuan:boolean, isDeletePicture:boolean, isDeleteLrc:boolean, context:Context, packName:string ) { const selectedFiles: VideoItem[] = JSON.parse(selectedFilesStr) if (ArrayUtil.isNotEmpty(selectedFiles)) { const table: MediaTable = new MediaTable(context) const playlistTable: PlaylistTable = new PlaylistTable(context) // 初始化数据库 await new Promise((resolve, reject) => { table.getRdbStore(context, (err:Error) => { err ? reject(err) : resolve(); }); }); let hasPlaylistChanges = false; // 标记是否有歌单变化 for (const item of selectedFiles) { console.info('onecold delete filePath = ' + item.filePath); if (item.type === CommonConstants.TYPE_IS_DIR) { table.deleteDataForParentPath(item.filePath, () => { FileUtil.rmdir(item.filePath).then(() => { return true }).catch((error:Error) => { console.error((error as Error).message); return false }); }); } else { table.deleteData(item, async () => { if(isDeleteYuan){ if(item.filePath.toLowerCase().includes(packName)){ await FileUtil.unlink(item.filePath) } } console.info(`onecold 封面文件=: ${item.pixelMapPath}`); if (isDeletePicture && item.pixelMapPath) { const picPath = FileUtil.getFilePath(item.pixelMapPath)//获取图 if (FileUtil.accessSync(picPath)) { await FileUtil.unlink(picPath); console.info(`onecold 封面文件已删除: ${picPath}`); } } const lyricPath = item.filePath?.replace(/\.[^/.]+$/, ".lrc"); console.info(`onecold 歌词文件=: ${lyricPath}`); if(isDeleteLrc) { if (lyricPath&&item.filePath.toLowerCase().includes(packName) && FileUtil.accessSync(lyricPath)) { await FileUtil.unlink(lyricPath); console.info( `onecold 歌词文件已删除: ${lyricPath}`); } } // this.onDeleteResult(true,item) }); // 先从所有歌单中移除这首歌(如果存在的话) LogUtil.info('heanup DeleteComptent', `准备从歌单中移除歌曲: ${item.name}, filePath: ${item.filePath}`); if (item.filePath) { try { const affectedPlaylists = await playlistTable.removeSongFromAllPlaylists(item.filePath); LogUtil.info('heanup DeleteComptent', `removeSongFromAllPlaylists 返回了 ${affectedPlaylists.length} 个受影响的歌单`); if (affectedPlaylists.length > 0) { hasPlaylistChanges = true; LogUtil.info('heanup DeleteComptent', `歌曲已从 ${affectedPlaylists.length} 个歌单中移除: ${item.name}`); } else { LogUtil.info('heanup DeleteComptent', `歌曲不在任何歌单中: ${item.name}`); } } catch (error) { LogUtil.error('heanup DeleteComptent', `从歌单移除歌曲失败: ${(error as Error).message}`); } } else { LogUtil.warn('heanup DeleteComptent', `歌曲 ${item.name} 的 filePath 为空`); } } } // 如果有歌单变化,发送刷新事件 if (hasPlaylistChanges) { const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }; emitter.emit(eventRefresh, {}); LogUtil.info('heanup DeleteComptent', '已发送歌单刷新事件,更新歌单数量'); } } return true }