| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267 |
- import { RemoteDriveManager } from '../common/util/RemoteDriveManager';
- import { WebDavAccount } from '../viewmodel/WebDavAccount';
- import { VideoItem } from '../viewmodel/VideoItem';
- import { FileInfo } from '../viewmodel/FileInfo';
- import { RemoteDriveManagerStates } from '../common/enums/RemoteDriveManagerStates';
- import Logger from '../common/util/Logger';
- import { router } from '@kit.ArkUI';
- import { CommonConstants } from '../common/constants/CommonConstants';
- import { Constants } from '../Constants';
- import { picker, fileUri } from '@kit.CoreFileKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { PreferencesUtil } from '@pura/harmony-utils';
- import { UploadTaskDataSource } from '../viewmodel/UploadTask';
- import { promptAction } from '@kit.ArkUI';
- import fs from '@ohos.file.fs';
- import ReqPermissionUtil from '../common/util/ReqPermissionUtil';
- import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil';
- import { SegmentButton } from '@kit.ArkUI';
- import { SegmentButtonOptions, SegmentButtonItemTuple } from '@kit.ArkUI';
- const TAG = 'heanup UploadMusicPage';
- export interface LocalFileItem {
- name: string;
- path: string;
- isDirectory: boolean;
- isAudioFile: boolean;
- }
- interface LocalAudioFile {
- fullPath: string;
- relativePath: string;
- }
- @Entry
- @Component
- export struct UploadMusicPage {
- @StorageProp('topSafeHeight') topSafeHeight: number = 0;
- @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
- @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
- @StorageProp('isDarkMode') isDarkMode: boolean = false;
- // 页面状态变量
- @State selectedFiles: string[] = [];
- @State selectedFileNames: string[] = [];
- @State selectedFileTypes: string[] = []; // 'file' 或 'folder'
- @State accounts: WebDavAccount[] = [];
- // 文件浏览器状态
- @State isShowFileBrowser: boolean = false;
- @State fileBrowserPath: string = '';
- @State browserFiles: LocalFileItem[] = [];
- @State selectedBrowserItems: Set<string> = new Set();
- @State isLoadingFiles: boolean = false;
- @State selectedAccount: WebDavAccount | null = null;
- @State uploadPath: string = '/';
- @State availablePaths: string[] = [];
- @State duplicateAction: string = 'skip';
- // SegmentButton 重复文件处理选项
- @State duplicateOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({
- buttons: [{ text: '跳过' }, { text: '覆盖' }, { text: '重命名' }] as SegmentButtonItemTuple,
- backgroundColor: $r('app.color.index_background'),
- selectedBackgroundColor: $r('app.color.start_window_background'),
- selectedFontColor: $r('app.color.text_color'),
- buttonPadding: { top: 10, bottom: 10 },
- multiply: false
- });
- @State @Watch('onDuplicateActionChange') selectedDuplicateIndex: number[] = [0];
- @State uploadProgress: number = 0;
- @State uploadSpeed: string = '0 KB/s';
- @State uploadedCount: number = 0;
- @State totalUploadCount: number = 0;
- @State isUploading: boolean = false;
- @State uploadQueue: VideoItem[] = [];
- @State finishQueue: VideoItem[] = [];
- @State currentTask: VideoItem | null = null;
- @State isShowPathBrowser: boolean = false;
- @State currentBrowsePath: string = '/';
- @State browseFolders: FileInfo[] = [];
- @State isLoadingFolders: boolean = false;
- @State pendingCount: number = 0;
- @State finishedCount: number = 0;
- // RemoteDriveManager实例
- private webdavManager: RemoteDriveManager = RemoteDriveManager.getInstance();
- // 性能优化:使用LazyDataSource优化队列列表渲染
- // LazyForEach配合IDataSource可以实现按需加载,避免一次性渲染大量列表项
- // 对于包含数百个上传任务的队列,可以显著提升UI响应性能
- private uploadQueueDataSource: UploadTaskDataSource = new UploadTaskDataSource();
- private finishQueueDataSource: UploadTaskDataSource = new UploadTaskDataSource();
- // 事件处理器引用
- private eventHandler: (event: string) => void = (event: string) => {
- this.handleUploadEvent(event);
- };
- private cachedDownloadRoot: string = '';
- private localBrowseRoot: string = '';
- // 重复文件处理方式变化监听
- onDuplicateActionChange() {
- if (this.selectedDuplicateIndex.length > 0) {
- const actionMap = ['skip', 'overwrite', 'rename'];
- this.duplicateAction = actionMap[this.selectedDuplicateIndex[0]];
- Logger.info(TAG, `重复文件处理方式变更为: ${this.duplicateAction}`);
- }
- }
- // 获取当前重复文件处理方式的描述
- private getActionDescription(): string {
- switch (this.duplicateAction) {
- case 'skip':
- return '保留远程文件,不上传';
- case 'overwrite':
- return '替换远程文件';
- case 'rename':
- return '自动重命名后上传';
- default:
- return '保留远程文件,不上传';
- }
- }
- aboutToAppear(): void {
- Logger.info(TAG, '页面即将显示');
- // 加载用户设置
- this.loadUserSettings();
- // 加载WebDAV账户列表
- this.loadAccounts();
- // 从路由参数获取预选账户ID
- const params = router.getParams() as Record<string, Object>;
- if (params && params['accountId']) {
- const accountId = params['accountId'] as number;
- this.preselectAccount(accountId);
- }
- // 订阅RemoteDriveManager的上传事件
- this.subscribeUploadEvents();
- }
- /**
- * 加载用户设置
- */
- private loadUserSettings(): void {
- try {
- // 加载默认重复文件处理方式
- this.duplicateAction = PreferencesUtil.getStringSync('webdavUploadDuplicateAction', 'skip');
- // 根据加载的设置初始化SegmentButton选择索引
- const actionMap = ['skip', 'overwrite', 'rename'];
- const index = actionMap.indexOf(this.duplicateAction);
- this.selectedDuplicateIndex = [index >= 0 ? index : 0];
- Logger.info(TAG, `加载用户设置 - 重复文件处理: ${this.duplicateAction}`);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `加载用户设置失败: ${err.message}`);
- }
- }
- aboutToDisappear(): void {
- Logger.info(TAG, '页面即将销毁');
- // 取消订阅事件
- this.unsubscribeUploadEvents();
- }
- /**
- * 加载WebDAV账户列表
- */
- private loadAccounts(): void {
- try {
- this.accounts = this.webdavManager.getAllWebDavAccounts();
- Logger.info(TAG, `加载了 ${this.accounts.length} 个WebDAV账户`);
- // 如果没有选中账户且有可用账户,默认选择第一个
- if (!this.selectedAccount && this.accounts.length > 0) {
- this.selectedAccount = this.accounts[0];
- this.applyDefaultUploadPath(this.selectedAccount);
- }
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `加载账户列表失败: ${err.message}`);
- }
- }
- private getPreferredDownloadRoot(): string {
- if (this.cachedDownloadRoot) {
- return this.cachedDownloadRoot;
- }
- try {
- let saved = PreferencesUtil.getStringSync('download_path', '');
- if (saved && saved.length > 0) {
- this.cachedDownloadRoot = this.normalizeLocalDirectory(saved);
- if (this.cachedDownloadRoot) {
- return this.cachedDownloadRoot;
- }
- }
- } catch (error) {
- const err = error as Error;
- Logger.warn(TAG, `读取download_path失败: ${err.message}`);
- }
- return '';
- }
- private normalizeLocalDirectory(path: string): string {
- if (!path || path.length === 0) {
- return '';
- }
- let normalized = path.replace(/\\/g, '/');
- normalized = normalized.replace(/\/+$/, '');
- return normalized;
- }
- private ensureDirectoryAccessible(path: string): boolean {
- if (!path || path.length === 0) {
- return false;
- }
- try {
- const stat = fs.statSync(path);
- return stat.isDirectory();
- } catch (_error) {
- try {
- fs.mkdirSync(path);
- return true;
- } catch (createErr) {
- const err = createErr as Error;
- Logger.warn(TAG, `创建目录失败(${path}): ${err.message}`);
- return false;
- }
- }
- }
- private async requestDownloadRootFromSystem(force: boolean = false): Promise<string> {
- try {
- const documentViewPicker = new picker.DocumentViewPicker();
- const documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD });
- if (documentSaveResult && documentSaveResult.length > 0) {
- const resolvedPath = new fileUri.FileUri(documentSaveResult[0]).path;
- const normalized = this.normalizeLocalDirectory(resolvedPath);
- if (normalized) {
- this.cachedDownloadRoot = normalized;
- const storedValue = normalized.endsWith('/') ? normalized : `${normalized}/`;
- PreferencesUtil.putSync('download_path', storedValue);
- await ReqPermissionUtil.persistPermission(documentSaveResult[0]);
- return normalized;
- }
- }
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `请求系统下载目录失败: ${err.message}`);
- }
- return '';
- }
- private async resolveInitialBrowserPath(): Promise<string> {
- let preferred = this.getPreferredDownloadRoot();
- if (preferred && this.ensureDirectoryAccessible(preferred)) {
- return preferred;
- }
- const requested = await this.requestDownloadRootFromSystem();
- if (requested && this.ensureDirectoryAccessible(requested)) {
- return requested;
- }
- promptAction.showToast({
- message: '请先在本地音乐页授权下载目录后再使用此功能',
- duration: 2000
- });
- throw new Error('未授权任何本地目录');
- }
- private normalizeBrowsePath(path: string): string {
- if (!path || path.trim().length === 0) {
- return '/';
- }
- let normalized = path.trim().replace(/\\/g, '/');
- if (!normalized.startsWith('/')) {
- normalized = `/${normalized}`;
- }
- normalized = normalized.replace(/\/+/g, '/');
- if (normalized.length > 1 && normalized.endsWith('/')) {
- normalized = normalized.slice(0, -1);
- }
- return normalized || '/';
- }
- /**
- * 预选指定账户
- * @param accountId 账户ID
- */
- private preselectAccount(accountId: number): void {
- try {
- for (let i = 0; i < this.accounts.length; i++) {
- const account = this.accounts[i];
- if (account.id === accountId) {
- this.selectedAccount = account;
- this.applyDefaultUploadPath(account);
- Logger.info(TAG, `预选账户: ${account.name}`);
- break;
- }
- }
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `预选账户失败: ${err.message}`);
- }
- }
- /**
- * 订阅RemoteDriveManager的上传事件
- */
- private subscribeUploadEvents(): void {
- this.webdavManager.subscribe(this.eventHandler);
- Logger.info(TAG, '已订阅上传事件');
- }
- /**
- * 取消订阅RemoteDriveManager的上传事件
- */
- private unsubscribeUploadEvents(): void {
- this.webdavManager.unsubscribe(this.eventHandler);
- Logger.info(TAG, '已取消订阅上传事件');
- }
- /**
- * 处理上传事件
- * @param event 事件类型
- */
- private handleUploadEvent(event: string): void {
- Logger.info(TAG, `收到上传事件: ${event}`);
- switch (event) {
- case RemoteDriveManagerStates.UploadStart:
- this.handleUploadStart();
- break;
- case RemoteDriveManagerStates.UploadProgress:
- this.handleUploadProgress();
- break;
- case RemoteDriveManagerStates.UploadSuccess:
- this.handleUploadSuccess();
- break;
- case RemoteDriveManagerStates.UploadFailed:
- this.handleUploadFailed();
- break;
- case RemoteDriveManagerStates.UploadPaused:
- this.handleUploadPaused();
- break;
- case RemoteDriveManagerStates.UploadResumed:
- this.handleUploadResumed();
- break;
- case RemoteDriveManagerStates.SetCurrentUploadTask:
- this.handleCurrentTaskChanged();
- break;
- case RemoteDriveManagerStates.ChangeUploadQueue:
- this.handleUploadQueueChanged();
- break;
- case RemoteDriveManagerStates.ChangeFinishUploadQueue:
- this.handleFinishQueueChanged();
- break;
- }
- }
- /**
- * 处理上传开始事件
- */
- private handleUploadStart(): void {
- this.isUploading = true;
- Logger.info(TAG, '上传开始');
- }
- /**
- * 处理上传进度更新事件
- */
- private handleUploadProgress(): void {
- const uploaded = this.webdavManager.uploadReceivedSize;
- const total = this.webdavManager.uploadTotalSize;
- if (total > 0) {
- this.uploadProgress = Math.floor((uploaded / total) * 100);
- // 计算上传速度(简化版,实际应该基于时间差计算)
- const speedMBps = uploaded / (1024 * 1024);
- this.uploadSpeed = `${speedMBps.toFixed(2)} MB/s`;
- }
- Logger.info(TAG, `上传进度: ${this.uploadProgress}%`);
- }
- /**
- * 处理上传成功事件
- */
- private handleUploadSuccess(): void {
- this.uploadedCount++;
- Logger.info(TAG, `上传成功,已完成: ${this.uploadedCount}/${this.totalUploadCount}`);
- }
- /**
- * 处理上传失败事件
- */
- private handleUploadFailed(): void {
- Logger.error(TAG, '上传失败');
- }
- /**
- * 处理上传暂停事件
- */
- private handleUploadPaused(): void {
- Logger.info(TAG, '上传已暂停');
- }
- /**
- * 处理上传恢复事件
- */
- private handleUploadResumed(): void {
- Logger.info(TAG, '上传已恢复');
- }
- /**
- * 处理当前任务变更事件
- */
- private handleCurrentTaskChanged(): void {
- const task = this.webdavManager.currentUploadTask;
- if (task) {
- this.currentTask = task.song;
- Logger.info(TAG, `当前上传任务: ${task.song.name}`);
- } else {
- this.currentTask = null;
- }
- }
- /**
- * 处理上传队列变更事件
- */
- private handleUploadQueueChanged(): void {
- const tasks = this.webdavManager.uploadQueue;
- this.uploadQueue = [];
- for (let i = 0; i < tasks.length; i++) {
- this.uploadQueue.push(tasks[i].song);
- }
- this.pendingCount = this.uploadQueue.length;
- if (this.pendingCount === 0) {
- this.isUploading = false;
- this.currentTask = null;
- this.uploadProgress = 0;
- this.uploadSpeed = '0 KB/s';
- }
- this.uploadQueueDataSource.updateTasks(this.uploadQueue);
- Logger.info(TAG, `上传队列更新,当前数量: ${this.pendingCount}`);
- }
- /**
- * 处理完成队列变更事件
- */
- private handleFinishQueueChanged(): void {
- const tasks = this.webdavManager.finishUploadQueue;
- this.finishQueue = [];
- for (let i = 0; i < tasks.length; i++) {
- this.finishQueue.push(tasks[i].song);
- }
- this.finishedCount = this.finishQueue.length;
- this.finishQueueDataSource.updateTasks(this.finishQueue);
- Logger.info(TAG, `完成队列更新,当前数量: ${this.finishedCount}`);
- }
- /**
- * 打开文件选择器
- */
- private async openFilePicker(): Promise<void> {
- try {
- const documentSelectOptions = new picker.DocumentSelectOptions();
- // 设置音频文件过滤器
- documentSelectOptions.fileSuffixFilters = Constants.AUDIO_EXTENSIONS;
- // 支持多选
- documentSelectOptions.maxSelectNumber = 100;
- const documentPicker = new picker.DocumentViewPicker();
- const documentSelectResult = await documentPicker.select(documentSelectOptions);
- if (documentSelectResult && documentSelectResult.length > 0) {
- for (let i = 0; i < documentSelectResult.length; i++) {
- try {
- await ReqPermissionUtil.persistPermission(documentSelectResult[i]);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `持久化授权失败: ${err.message}`);
- }
- }
- Logger.info(TAG, `选择了 ${documentSelectResult.length} 个文件`);
- // 保存选中的文件URI
- this.selectedFiles = documentSelectResult;
- // 提取文件名用于显示
- this.selectedFileNames = [];
- this.selectedFileTypes = [];
- for (let i = 0; i < documentSelectResult.length; i++) {
- const uri = documentSelectResult[i];
- try {
- const fileUriObj = new fileUri.FileUri(uri);
- const fileName = fileUriObj.name;
- this.selectedFileNames.push(fileName);
- this.selectedFileTypes.push('file');
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `解析文件URI失败: ${err.message}`);
- this.selectedFileNames.push('未知文件');
- this.selectedFileTypes.push('file');
- }
- }
- Logger.info(TAG, `文件列表: ${JSON.stringify(this.selectedFileNames)}`);
- }
- } catch (error) {
- const err = error as BusinessError;
- Logger.error(TAG, `文件选择失败: ${err.message}`);
- }
- }
- /**
- * 打开文件浏览器
- */
- private async openFileBrowser(): Promise<void> {
- try {
- const initialPath = await this.resolveInitialBrowserPath();
- if (!initialPath) {
- promptAction.showToast({
- message: '无法定位到下载目录,请授予文件访问权限',
- duration: 2000
- });
- return;
- }
- this.fileBrowserPath = initialPath;
- this.localBrowseRoot = initialPath;
- this.selectedBrowserItems.clear();
- this.isShowFileBrowser = true;
- await this.loadLocalFiles(initialPath);
- Logger.info(TAG, `文件浏览器已定位到: ${initialPath}`);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `打开文件浏览器失败: ${err.message}`);
- promptAction.showToast({
- message: err.message || '打开文件浏览器失败',
- duration: 2000
- });
- }
- }
- /**
- * 加载本地文件列表
- */
- private async loadLocalFiles(path: string): Promise<void> {
- try {
- this.isLoadingFiles = true;
- this.browserFiles = [];
- let targetPath = path;
- if (this.localBrowseRoot && !targetPath.startsWith(this.localBrowseRoot)) {
- targetPath = this.localBrowseRoot;
- }
- Logger.info(TAG, `加载目录: ${targetPath}`);
- const files = fs.listFileSync(targetPath);
- for (let i = 0; i < files.length; i++) {
- const fileName = files[i];
- const fullPath = `${targetPath}/${fileName}`;
- try {
- const stat = fs.statSync(fullPath);
- const isDirectory = stat.isDirectory();
- if (fileName.startsWith('.')) {
- continue;
- }
- const lowerFileName = fileName.toLowerCase();
- const isAudioFile = !isDirectory && Constants.AUDIO_EXTENSIONS.some(ext => lowerFileName.endsWith(ext));
- // 只显示文件夹和音频文件
- if (isDirectory || isAudioFile) {
- this.browserFiles.push({
- name: fileName,
- path: fullPath,
- isDirectory: isDirectory,
- isAudioFile: isAudioFile
- });
- }
- } catch (error) {
- Logger.warn(TAG, `无法访问: ${fullPath}`);
- }
- }
- // 排序:文件夹在前,文件在后
- this.browserFiles.sort((a, b) => {
- if (a.isDirectory && !b.isDirectory) return -1;
- if (!a.isDirectory && b.isDirectory) return 1;
- return a.name.localeCompare(b.name);
- });
- Logger.info(TAG, `加载了 ${this.browserFiles.length} 个项目`);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `加载文件列表失败: ${err.message}`);
- promptAction.showToast({
- message: `加载失败: ${err.message}`,
- duration: 2000
- });
- } finally {
- this.isLoadingFiles = false;
- }
- }
- /**
- * 进入子目录
- */
- private async enterDirectory(item: LocalFileItem): Promise<void> {
- if (!item.isDirectory) {
- return;
- }
- if (this.localBrowseRoot && !item.path.startsWith(this.localBrowseRoot)) {
- promptAction.showToast({
- message: '没有此目录的访问权限',
- duration: 2000
- });
- return;
- }
- this.fileBrowserPath = item.path;
- await this.loadLocalFiles(item.path);
- }
- /**
- * 返回上级目录
- */
- private async goBackDirectory(): Promise<void> {
- if (!this.localBrowseRoot) {
- return;
- }
- if (this.fileBrowserPath === this.localBrowseRoot) {
- return;
- }
- const lastSlash = this.fileBrowserPath.lastIndexOf('/');
- if (lastSlash > 0) {
- const parent = this.fileBrowserPath.substring(0, lastSlash);
- if (!parent.startsWith(this.localBrowseRoot)) {
- this.fileBrowserPath = this.localBrowseRoot;
- await this.loadLocalFiles(this.localBrowseRoot);
- return;
- }
- this.fileBrowserPath = parent;
- await this.loadLocalFiles(this.fileBrowserPath);
- }
- }
- /**
- * 切换项目选中状态
- */
- private toggleItemSelection(item: LocalFileItem): void {
- if (this.selectedBrowserItems.has(item.path)) {
- this.selectedBrowserItems.delete(item.path);
- } else {
- this.selectedBrowserItems.add(item.path);
- }
- // 触发UI更新
- this.selectedBrowserItems = new Set(this.selectedBrowserItems);
- }
- /**
- * 确认选择文件
- */
- private async confirmFileSelection(): Promise<void> {
- if (this.selectedBrowserItems.size === 0) {
- promptAction.showToast({
- message: '请至少选择一个项目',
- duration: 2000
- });
- return;
- }
- // 将选中的项目添加到已选列表
- const selectedItems: LocalFileItem[] = [];
- const selectedSet = new Set<string>();
- this.selectedBrowserItems.forEach((path) => {
- const found = this.browserFiles.find((f) => f.path === path);
- if (found && !selectedSet.has(found.path)) {
- selectedSet.add(found.path);
- selectedItems.push(found);
- }
- });
- for (let i = 0; i < selectedItems.length; i++) {
- const item = selectedItems[i];
- const uri = `file://${item.path}`;
- try {
- await ReqPermissionUtil.persistPermission(uri);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `持久化授权失败: ${err.message}`);
- }
- this.selectedFiles.push(uri);
- this.selectedFileNames.push(item.name);
- this.selectedFileTypes.push(item.isDirectory ? 'folder' : 'file');
- }
- promptAction.showToast({
- message: `已添加 ${this.selectedBrowserItems.size} 个项目`,
- duration: 2000
- });
- this.closeFileBrowser();
- }
- /**
- * 关闭文件浏览器
- */
- private closeFileBrowser(): void {
- this.isShowFileBrowser = false;
- this.selectedBrowserItems.clear();
- this.browserFiles = [];
- this.localBrowseRoot = '';
- }
- /**
- * 递归扫描文件夹,获取所有音频文件
- */
- private async scanFolderForAudioFiles(folderPath: string, relativePrefix: string = ''): Promise<LocalAudioFile[]> {
- const audioFiles: LocalAudioFile[] = [];
- try {
- const files = fs.listFileSync(folderPath);
- for (let i = 0; i < files.length; i++) {
- const fileName = files[i];
- const fullPath = `${folderPath}/${fileName}`;
- try {
- const stat = fs.statSync(fullPath);
- if (stat.isDirectory()) {
- const nextPrefix = relativePrefix ? `${relativePrefix}/${fileName}` : fileName;
- const subFiles = await this.scanFolderForAudioFiles(fullPath, nextPrefix);
- audioFiles.push(...subFiles);
- } else {
- const lowerFileName = fileName.toLowerCase();
- const isAudio = Constants.AUDIO_EXTENSIONS.some(ext => lowerFileName.endsWith(ext));
- if (isAudio) {
- const relativePath = relativePrefix ? `${relativePrefix}/${fileName}` : fileName;
- audioFiles.push({
- fullPath,
- relativePath
- });
- }
- }
- } catch (error) {
- const err = error as Error;
- Logger.warn(TAG, `无法访问: ${fullPath}, 错误: ${err.message}`);
- }
- }
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `扫描文件夹失败: ${err.message}`);
- }
- return audioFiles;
- }
- private getFileNameFromPath(fullPath: string): string {
- if (!fullPath) {
- return '';
- }
- const normalized = fullPath.replace(/\\/g, '/');
- const lastSlash = normalized.lastIndexOf('/');
- return lastSlash >= 0 ? normalized.substring(lastSlash + 1) : normalized;
- }
- /**
- * 移除已选文件
- * @param index 文件索引
- */
- private removeSelectedFile(index: number): void {
- if (index >= 0 && index < this.selectedFiles.length) {
- this.selectedFiles.splice(index, 1);
- this.selectedFileNames.splice(index, 1);
- this.selectedFileTypes.splice(index, 1);
- Logger.info(TAG, `移除项目,剩余 ${this.selectedFiles.length} 个`);
- }
- }
- /**
- * 选择上传账户
- * @param account 选中的账户
- */
- private selectUploadAccount(account: WebDavAccount): void {
- this.selectedAccount = account;
- this.applyDefaultUploadPath(account);
- Logger.info(TAG, `选择账户: ${account.name}, 上传路径: ${this.uploadPath}`);
- }
- private applyDefaultUploadPath(account: WebDavAccount | null): void {
- if (!account) {
- return;
- }
- const managerAccount = this.webdavManager.currentAccount;
- const managerPath = this.webdavManager.currentPath;
- if (
- managerPath &&
- managerPath.length > 0 &&
- managerAccount &&
- managerAccount.id === account.id
- ) {
- this.uploadPath = managerPath;
- } else {
- this.uploadPath = account.uploadFilePath || '/';
- }
- }
- /**
- * 打开路径浏览器
- */
- private openPathBrowser(): void {
- if (!this.selectedAccount) {
- Logger.warn(TAG, '请先选择账户');
- return;
- }
- Logger.info(TAG, '========== 打开路径浏览器 ==========');
- Logger.info(TAG, `当前上传路径: ${this.uploadPath}`);
- Logger.info(TAG, `选中账户: ${this.selectedAccount.name}`);
- const managerAccount = this.webdavManager.currentAccount;
- const defaultPath =
- managerAccount &&
- managerAccount.id === this.selectedAccount.id &&
- this.webdavManager.currentPath &&
- this.webdavManager.currentPath.length > 0
- ? this.webdavManager.currentPath
- : this.uploadPath;
- this.currentBrowsePath = defaultPath;
- this.isShowPathBrowser = true;
- Logger.info(TAG, `isShowPathBrowser 设置为: ${this.isShowPathBrowser}`);
- Logger.info(TAG, '开始加载文件夹列表...');
- this.loadWebDavFolders(this.currentBrowsePath);
- }
- /**
- * 关闭路径浏览器
- */
- private closePathBrowser(): void {
- Logger.info(TAG, '========== 关闭路径浏览器 ==========');
- this.isShowPathBrowser = false;
- this.browseFolders = [];
- }
- /**
- * 加载WebDAV文件夹列表
- * @param path 路径
- */
- private async loadWebDavFolders(path: string): Promise<void> {
- if (!this.selectedAccount) {
- return;
- }
- try {
- this.isLoadingFolders = true;
- Logger.info(TAG, `加载文件夹列表: ${path}`);
- await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount, path);
- // 过滤出文件夹
- const allFiles = this.webdavManager.webDavFiles;
- this.browseFolders = [];
- const normalizedCurrent = this.normalizeBrowsePath(this.currentBrowsePath);
- for (let i = 0; i < allFiles.length; i++) {
- const file = allFiles[i];
- if (file.isDirectory) {
- const folderPath = this.normalizeBrowsePath(file.href);
- if (folderPath !== normalizedCurrent) {
- this.browseFolders.push(file);
- }
- }
- }
- Logger.info(TAG, `加载了 ${this.browseFolders.length} 个文件夹`);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `加载文件夹失败: ${err.message}`);
- } finally {
- this.isLoadingFolders = false;
- }
- }
- /**
- * 进入子文件夹
- * @param folder 文件夹信息
- */
- private async enterBrowseFolder(folder: FileInfo): Promise<void> {
- this.currentBrowsePath = folder.href;
- await this.loadWebDavFolders(folder.href);
- }
- /**
- * 返回上级文件夹
- */
- private async goBackBrowseFolder(): Promise<void> {
- if (this.currentBrowsePath === '/') {
- return;
- }
- const lastSlashIndex = this.currentBrowsePath.lastIndexOf('/');
- if (lastSlashIndex > 0) {
- this.currentBrowsePath = this.currentBrowsePath.substring(0, lastSlashIndex);
- } else {
- this.currentBrowsePath = '/';
- }
- await this.loadWebDavFolders(this.currentBrowsePath);
- }
- /**
- * 确认选择路径
- */
- private confirmPathSelection(): void {
- this.uploadPath = this.currentBrowsePath;
- Logger.info(TAG, `选择上传路径: ${this.uploadPath}`);
- this.closePathBrowser();
- }
- /**
- * 开始上传
- */
- private async startUpload(): Promise<void> {
- // 验证
- if (!this.selectedAccount) {
- Logger.warn(TAG, '请先选择账户');
- return;
- }
- if (this.selectedFiles.length === 0) {
- Logger.warn(TAG, '请先选择文件');
- return;
- }
- if (!this.uploadPath) {
- Logger.warn(TAG, '请先选择上传路径');
- return;
- }
- try {
- Logger.info(TAG, '开始上传任务');
- // 显示处理提示
- promptAction.showToast({
- message: '正在处理文件...',
- duration: 2000
- });
- // 构建VideoItem列表
- const songs: VideoItem[] = [];
- let totalFiles = 0;
- let totalFolders = 0;
- for (let i = 0; i < this.selectedFiles.length; i++) {
- const selectedUri = this.selectedFiles[i];
- const displayName = this.selectedFileNames[i];
- const itemType = this.selectedFileTypes[i];
- try {
- const fileUriObj = new fileUri.FileUri(selectedUri);
- const resolvedPath = fileUriObj.path || '';
- if (!resolvedPath) {
- Logger.error(TAG, `无法获取本地路径,跳过: ${selectedUri}`);
- continue;
- }
- if (itemType === 'folder') {
- totalFolders++;
- const folderName = displayName || this.getFileNameFromPath(resolvedPath);
- Logger.info(TAG, `扫描文件夹: ${folderName}`);
- const audioFiles = await this.scanFolderForAudioFiles(resolvedPath, folderName);
- Logger.info(TAG, `文件夹 ${folderName} 包含 ${audioFiles.length} 个音频文件`);
- for (let j = 0; j < audioFiles.length; j++) {
- const audioFile = audioFiles[j];
- const audioFileName = this.getFileNameFromPath(audioFile.fullPath);
- const videoItem = new VideoItem(
- audioFileName,
- `file://${audioFile.fullPath}`,
- audioFile.fullPath,
- CommonConstants.TYPE_LOCAL,
- 0,
- '',
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined
- );
- videoItem.remote_rel_path = audioFile.relativePath;
- songs.push(videoItem);
- }
- } else {
- // 处理单个文件
- totalFiles++;
- const videoItem = new VideoItem(
- displayName,
- selectedUri,
- resolvedPath,
- CommonConstants.TYPE_LOCAL,
- 0,
- '',
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- undefined
- );
- videoItem.remote_rel_path = displayName;
- songs.push(videoItem);
- }
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `处理项目失败: ${err.message}`);
- continue;
- }
- }
- if (songs.length === 0) {
- promptAction.showToast({
- message: '未找到可上传的音频文件',
- duration: 2000
- });
- Logger.warn(TAG, '未找到可上传的有效文件,取消任务');
- return;
- }
- Logger.info(TAG, `处理完成: ${totalFiles} 个文件, ${totalFolders} 个文件夹, 共 ${songs.length} 个音频文件`);
- promptAction.showToast({
- message: `准备上传 ${songs.length} 个文件`,
- duration: 2000
- });
- // 添加到上传队列,传递用户选择的上传路径
- Logger.info(TAG, `使用上传路径: ${this.uploadPath}`);
- const uniqueSongs = this.deduplicateSongs(songs);
- this.webdavManager.addToUploadQueue(uniqueSongs, this.selectedAccount, this.uploadPath);
- this.totalUploadCount = uniqueSongs.length;
- this.uploadedCount = 0;
- // 开始处理上传队列
- await this.webdavManager.startUploadQueue();
- Logger.info(TAG, '上传任务已启动');
- this.selectedFiles = [];
- this.selectedFileNames = [];
- this.selectedFileTypes = [];
- this.pendingCount = this.webdavManager.uploadQueue.length;
- this.selectedFiles = [];
- this.selectedFileNames = [];
- this.selectedFileTypes = [];
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `启动上传失败: ${err.message}`);
- promptAction.showToast({
- message: `上传失败: ${err.message}`,
- duration: 2000
- });
- }
- }
- private deduplicateSongs(songs: VideoItem[]): VideoItem[] {
- const unique: VideoItem[] = [];
- const seen = new Set<string>();
- for (let i = 0; i < songs.length; i++) {
- const song = songs[i];
- const key = song.filePath || song.name;
- if (!key) {
- continue;
- }
- if (seen.has(key)) {
- Logger.warn(TAG, `跳过重复文件: ${song.name}`);
- continue;
- }
- seen.add(key);
- unique.push(song);
- }
- return unique;
- }
- /**
- * 暂停上传
- */
- private pauseUpload(): void {
- this.webdavManager.pauseUploadQueue();
- Logger.info(TAG, '暂停上传');
- }
- /**
- * 恢复上传
- */
- private async resumeUpload(): Promise<void> {
- await this.webdavManager.resumeUploadQueue();
- Logger.info(TAG, '恢复上传');
- }
- /**
- * 取消上传
- */
- private cancelUpload(): void {
- this.webdavManager.pauseUploadQueue();
- this.webdavManager.clearUploadQueue();
- this.isUploading = false;
- this.currentTask = null;
- Logger.info(TAG, '取消上传');
- }
- /**
- * 上传进度显示组件
- */
- @Builder
- UploadProgressView() {
- if (this.isUploading && this.currentTask) {
- Column({ space: 16 }) {
- // 标题行
- Row({ space: 8 }) {
- Text('⏫')
- .fontSize(20)
- Text('上传中')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- // 当前文件信息卡片
- Column({ space: 10 }) {
- Row({ space: 8 }) {
- Text('🎵')
- .fontSize(18)
- Text(this.currentTask.name)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- .layoutWeight(1)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- .width('100%')
- // 进度条
- Progress({ value: this.uploadProgress, total: 100, type: ProgressType.Linear })
- .width('100%')
- .color(this.themeColor)
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#E5E5EA')
- .style({ strokeWidth: 6 })
- // 进度信息行
- Row() {
- Text(`${this.uploadProgress}%`)
- .fontSize(15)
- .fontWeight(FontWeight.Bold)
- .fontColor(this.themeColor)
- Blank()
- Text(`${this.uploadSpeed}`)
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#8E8E93')
- }
- .width('100%')
- // 已上传数量
- Row({ space: 6 }) {
- Text('✓')
- .fontSize(14)
- .fontColor(this.themeColor)
- Text(`已完成 ${this.uploadedCount}/${this.totalUploadCount}`)
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#8E8E93')
- }
- }
- .width('100%')
- .padding(14)
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#F5F7FA')
- .borderRadius(10)
- // 控制按钮
- Row({ space: 10 }) {
- Button({ type: ButtonType.Normal }) {
- Row({ space: 6 }) {
- Text(this.webdavManager.isPauseUpload ? '▶️' : '⏸️')
- .fontSize(16)
- Text(this.webdavManager.isPauseUpload ? '恢复' : '暂停')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- }
- }
- .height(44)
- .layoutWeight(1)
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(10)
- .onClick(() => {
- if (this.webdavManager.isPauseUpload) {
- this.resumeUpload();
- } else {
- this.pauseUpload();
- }
- })
- Button({ type: ButtonType.Normal }) {
- Row({ space: 6 }) {
- Text('❌')
- .fontSize(16)
- Text('取消')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- }
- }
- .height(44)
- .layoutWeight(1)
- .backgroundColor(this.isDarkMode ? '#D94838' : '#FF3B30')
- .fontColor('#FFFFFF')
- .borderRadius(10)
- .onClick(() => {
- this.cancelUpload();
- })
- }
- .width('100%')
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .borderRadius(12)
- .shadow({
- radius: this.isDarkMode ? 8 : 12,
- color: this.isDarkMode ? '#0C000000' : '#19000000',
- offsetX: 0,
- offsetY: 2
- })
- .margin({ bottom: 16 })
- }
- }
- /**
- * 上传队列管理组件(使用LazyDataSource优化性能)
- */
- @Builder
- UploadQueueView() {
- Column({ space: 16 }) {
- // 标题行
- Row({ space: 8 }) {
- Text('📊')
- .fontSize(20)
- Text('任务队列')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- Tabs({ barPosition: BarPosition.Start }) {
- TabContent() {
- this.QueueList(this.uploadQueueDataSource, 'pending')
- }
- .tabBar(this.TabBarBuilder('⏳', '待上传', this.pendingCount))
- TabContent() {
- this.QueueList(this.finishQueueDataSource, 'finished')
- }
- .tabBar(this.TabBarBuilder('✅', '已完成', this.finishedCount))
- }
- .width('100%')
- .height(300)
- .barMode(BarMode.Fixed)
- .barBackgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .animationDuration(300)
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .borderRadius(12)
- .shadow({
- radius: this.isDarkMode ? 8 : 12,
- color: this.isDarkMode ? '#0C000000' : '#19000000',
- offsetX: 0,
- offsetY: 2
- })
- .margin({ bottom: 16 })
- }
- /**
- * 自定义Tab标签构建器
- */
- @Builder
- TabBarBuilder(icon: string, title: string, count: number) {
- Row({ space: 6 }) {
- Text(icon)
- .fontSize(16)
- Text(title)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- Text(`${count}`)
- .fontSize(12)
- .fontWeight(FontWeight.Medium)
- .fontColor('#FFFFFF')
- .padding({
- left: 6,
- right: 6,
- top: 2,
- bottom: 2
- })
- .backgroundColor(this.themeColor)
- .borderRadius(8)
- }
- .padding({ left: 4, right: 4 })
- }
- /**
- * 队列列表组件(使用LazyForEach优化性能)
- */
- @Builder
- QueueList(dataSource: UploadTaskDataSource, type: string) {
- if (dataSource.totalCount() === 0) {
- Column() {
- Text(type === 'pending' ? '暂无待上传任务' : '暂无已完成任务')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#66000000')
- }
- .width('100%')
- .height('100%')
- .justifyContent(FlexAlign.Center)
- } else {
- List({ space: 8 }) {
- LazyForEach(dataSource, (item: VideoItem, index: number) => {
- ListItem() {
- Row({ space: 12 }) {
- // 文件图标
- Text('🎵')
- .fontSize(20)
- Text(item.name)
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- .layoutWeight(1)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- if (type === 'pending') {
- Button('移除')
- .fontSize(12)
- .height(32)
- .padding({ left: 12, right: 12 })
- .backgroundColor(this.isDarkMode ? '#D94838' : '#E84026')
- .fontColor('#FFFFFF')
- .borderRadius(6)
- .onClick(() => {
- // 从队列移除
- const tasks = this.webdavManager.uploadQueue;
- if (index < tasks.length) {
- this.webdavManager.removeFromUploadQueue(tasks[index]);
- }
- })
- } else {
- Text('✓')
- .fontSize(20)
- .fontColor(this.isDarkMode ? '#5BA854' : '#64BB5C')
- }
- }
- .width('100%')
- .padding(12)
- .backgroundColor(this.isDarkMode ? '#2E3033' : '#F1F3F5')
- .borderRadius(8)
- }
- }, (item: VideoItem, index: number) => `${type}-${index}-${item.name}`)
- }
- .width('100%')
- .height('100%')
- .cachedCount(5) // 缓存5个列表项以提升滚动性能
- .divider({
- strokeWidth: 1,
- color: this.isDarkMode ? '#19FFFFFF' : '#0C000000'
- })
- }
- }
- /**
- * 开始上传按钮组件
- */
- @Builder
- StartUploadButton() {
- Button({ type: ButtonType.Normal }) {
- Row({ space: 10 }) {
- Text('🚀')
- .fontSize(20)
- Text('开始上传')
- .fontSize(16)
- .fontWeight(FontWeight.Bold)
- }
- }
- .width('100%')
- .height(54)
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(12)
- .enabled(!this.isUploading && this.selectedFiles.length > 0 && this.selectedAccount !== null)
- .opacity((!this.isUploading && this.selectedFiles.length > 0 && this.selectedAccount !== null) ? 1.0 : 0.4)
- .shadow({
- radius: 12,
- color: this.themeColor + '50',
- offsetX: 0,
- offsetY: 4
- })
- .onClick(() => {
- this.startUpload();
- })
- .margin({ bottom: 16 })
- }
- /**
- * 远程配置区域组件
- */
- @Builder
- UploadConfigSection() {
- Column({ space: 16 }) {
- // 标题行
- Row({ space: 8 }) {
- Text('📁')
- .fontSize(20)
- Text('远程配置')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- // 上传路径选择卡片
- Column({ space: 12 }) {
- Row({ space: 8 }) {
- Text('远程目录')
- .fontSize(13)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#66000000')
- Row({ space: 8 }) {
- Text('📂')
- .fontSize(18)
- Text(this.uploadPath || '请选择远程目录')
- .fontSize(15)
- .fontColor(this.uploadPath ?
- (this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F') :
- (this.isDarkMode ? '#66FFFFFF' : '#99000000'))
- .fontWeight(this.uploadPath ? FontWeight.Medium : FontWeight.Regular)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- Blank()
- Button('浏览')
- .fontSize(13)
- .height(32)
- .padding({ left: 12, right: 12 })
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(6)
- .enabled(this.selectedAccount !== null)
- .opacity(this.selectedAccount !== null ? 1.0 : 0.4)
- .bindSheet($$this.isShowPathBrowser, this.PathBrowserDialog(), {
- height: '90%',
- dragBar: true,
- showClose: true,
- preferType: SheetType.BOTTOM,
- blurStyle: BlurStyle.Thin,
- title: { title: '选择上传路径' }
- })
- .onClick(() => {
- this.openPathBrowser();
- })
- }
- .width('100%')
- }
- .width('100%')
- .padding(14)
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#F5F7FA')
- .borderRadius(10)
- .border({
- width: 1,
- color: this.uploadPath ?
- (this.isDarkMode ? this.themeColor + '40' : this.themeColor + '30') :
- (this.isDarkMode ? '#19FFFFFF' : '#E5E5EA')
- })
- // 重复文件处理卡片
- Column({ space: 12 }) {
- Text('重复文件处理')
- .fontSize(13)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#66000000')
- .alignSelf(ItemAlign.Start)
- // SegmentButton 选项
- SegmentButton({
- options: this.duplicateOptions,
- selectedIndexes: $selectedDuplicateIndex
- })
- .width('100%')
- .margin({ top: 8 })
- }
- .width('100%')
- .padding(14)
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#F5F7FA')
- .borderRadius(10)
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .borderRadius(12)
- .shadow({
- radius: this.isDarkMode ? 8 : 12,
- color: this.isDarkMode ? '#0C000000' : '#19000000',
- offsetX: 0,
- offsetY: 2
- })
- .margin({ bottom: 16 })
- }
- /**
- * 路径浏览器对话框
- */
- @Builder
- PathBrowserDialog() {
- Column() {
- // 当前路径显示
- Row({ space: 12 }) {
- Column({ space: 4 }) {
- Text('当前路径')
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#66000000')
- Text(this.currentBrowsePath)
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- if (this.currentBrowsePath !== '/') {
- Button('返回上级')
- .fontSize(12)
- .height(36)
- .padding({ left: 12, right: 12 })
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(8)
- .onClick(() => {
- this.goBackBrowseFolder();
- })
- }
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#191A1C' : '#F1F3F5')
- // 文件夹列表
- if (this.isLoadingFolders) {
- Column({ space: 12 }) {
- LoadingProgress()
- .width(48)
- .height(48)
- .color(this.themeColor)
- Text('加载中...')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#99000000')
- }
- .width('100%')
- .height(300)
- .justifyContent(FlexAlign.Center)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- } else if (this.browseFolders.length === 0) {
- Column() {
- Text('📂')
- .fontSize(48)
- .margin({ bottom: 12 })
- Text('当前目录没有子文件夹')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#66000000')
- }
- .width('100%')
- .height(300)
- .justifyContent(FlexAlign.Center)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- } else {
- List({ space: 8 }) {
- ForEach(this.browseFolders, (folder: FileInfo, index: number) => {
- ListItem() {
- Row({ space: 12 }) {
- Text('📁')
- .fontSize(24)
- Text(folder.fileName)
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- .layoutWeight(1)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- Text('→')
- .fontSize(18)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#66000000')
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#2E3033' : '#F1F3F5')
- .borderRadius(8)
- .onClick(() => {
- this.enterBrowseFolder(folder);
- })
- }
- }, (folder: FileInfo, index: number) => `folder-${index}-${folder.fileName}`)
- }
- .width('100%')
- .layoutWeight(1)
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .divider({
- strokeWidth: 1,
- color: this.isDarkMode ? '#19FFFFFF' : '#0C000000'
- })
- }
- // 底部按钮
- Row({ space: 12 }) {
- Button('取消')
- .fontSize(14)
- .height(48)
- .layoutWeight(1)
- .backgroundColor(this.isDarkMode ? '#2E3033' : '#E5E5EA')
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- .borderRadius(8)
- .onClick(() => {
- this.closePathBrowser();
- })
- Button('选择此路径')
- .fontSize(14)
- .height(48)
- .layoutWeight(1)
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(8)
- .onClick(() => {
- this.confirmPathSelection();
- })
- }
- .width('100%')
- .padding({ left:16,right:16 })
- .margin({bottom:25})
- }
- }
- /**
- * 账户选择区域组件
- */
- @Builder
- AccountSelectorSection() {
- Column({ space: 16 }) {
- // 标题行
- Row({ space: 8 }) {
- Text('☁️')
- .fontSize(20)
- Text('选择账户')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- if (this.accounts.length === 0) {
- Column({ space: 12 }) {
- Text('📦')
- .fontSize(48)
- Text('暂无账户')
- .fontSize(16)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#99000000')
- Text('请先添加账户')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#66000000')
- }
- .width('100%')
- .padding(32)
- .justifyContent(FlexAlign.Center)
- } else {
- List({ space: 8 }) {
- ForEach(this.accounts, (account: WebDavAccount, index: number) => {
- ListItem() {
- Row({ space: 12 }) {
- // 账户图标
- Text('☁️')
- .fontSize(24)
- Column({ space: 3 }) {
- Text(account.name)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- Text(`${account.host}:${account.port}`)
- .fontSize(11)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- if (this.selectedAccount && this.selectedAccount.id === account.id) {
- Text('✓')
- .fontSize(20)
- .fontColor(this.themeColor)
- }
- }
- .width('100%')
- .padding(8)
- .backgroundColor(this.selectedAccount && this.selectedAccount.id === account.id
- ? (this.isDarkMode ? '#2E3238' : '#FFFFFF')
- : (this.isDarkMode ? '#2E3238' : '#F5F7FA'))
- .borderRadius(10)
- .border({
- width: this.selectedAccount && this.selectedAccount.id === account.id ? 2 : 1,
- color: this.selectedAccount && this.selectedAccount.id === account.id
- ? this.themeColor
- : (this.isDarkMode ? '#19FFFFFF' : '#E5E5EA')
- })
- .onClick(() => {
- this.selectUploadAccount(account);
- })
- }
- }, (account: WebDavAccount) => `account-${account.id}`)
- }
- .width('100%')
- }
- }
- .width('100%')
- .padding(10)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .borderRadius(12)
- .shadow({
- radius: this.isDarkMode ? 8 : 12,
- color: this.isDarkMode ? '#0C000000' : '#19000000',
- offsetX: 0,
- offsetY: 2
- })
- .margin({ bottom: 16 })
- }
- /**
- * 文件选择区域组件
- */
- @Builder
- FilePickerSection() {
- Column({ space: 16 }) {
- // 标题行
- Row({ space: 8 }) {
- Text('🎵')
- .fontSize(20)
- Text('选择文件')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- // 按钮组
- Row({ space: 10 }) {
- Button({ type: ButtonType.Normal }) {
- Row({ space: 6 }) {
- Text('📁')
- .fontSize(16)
- Text('从文件管理器')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- }
- }
- .layoutWeight(1)
- .height(48)
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(10)
- .shadow({
- radius: 8,
- color: this.themeColor + '40',
- offsetX: 0,
- offsetY: 3
- })
- .onClick(() => {
- this.openFilePicker();
- })
- Button({ type: ButtonType.Normal }) {
- Row({ space: 6 }) {
- Text('🎵')
- .fontSize(16)
- Text('从本地目录')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- }
- }
- .layoutWeight(1)
- .height(48)
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(10)
- .shadow({
- radius: 8,
- color: this.themeColor + '40',
- offsetX: 0,
- offsetY: 3
- })
- .onClick(() => {
- this.openFileBrowser();
- })
- }
- .width('100%')
- if (this.selectedFiles.length > 0) {
- Row({ space: 10 }) {
- Text('✓')
- .fontSize(18)
- .fontColor(this.themeColor)
- Column({ space: 2 }) {
- Text(`已选择 ${this.selectedFiles.length} 项`)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- Row({ space: 8 }) {
- if (this.selectedFileTypes.filter(t => t === 'file').length > 0) {
- Text(`${this.selectedFileTypes.filter(t => t === 'file').length} 个文件`)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- }
- if (this.selectedFileTypes.filter(t => t === 'folder').length > 0) {
- Text(`${this.selectedFileTypes.filter(t => t === 'folder').length} 个文件夹`)
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- }
- }
- }
- .alignItems(HorizontalAlign.Start)
- }
- .width('100%')
- .padding(12)
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#F5F7FA')
- .borderRadius(8)
- .border({
- width: 1,
- color: this.isDarkMode ? this.themeColor + '40' : this.themeColor + '30'
- })
- }
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .borderRadius(12)
- .shadow({
- radius: this.isDarkMode ? 8 : 12,
- color: this.isDarkMode ? '#0C000000' : '#19000000',
- offsetX: 0,
- offsetY: 2
- })
- .margin({ bottom: 16 })
- }
- /**
- * 已选文件列表组件
- */
- @Builder
- SelectedFilesList() {
- Column({ space: 16 }) {
- // 标题行
- Row({ space: 8 }) {
- Text('📋')
- .fontSize(20)
- Text('已选文件')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- Blank()
- Text(`${this.selectedFiles.length}`)
- .fontSize(13)
- .fontWeight(FontWeight.Medium)
- .fontColor('#FFFFFF')
- .padding({
- left: 8,
- right: 8,
- top: 4,
- bottom: 4
- })
- .backgroundColor(this.themeColor)
- .borderRadius(10)
- }
- .width('100%')
- .alignSelf(ItemAlign.Start)
- List({ space: 8 }) {
- ForEach(this.selectedFileNames, (fileName: string, index: number) => {
- ListItem() {
- Row({ space: 12 }) {
- // 文件/文件夹图标
- Text(this.selectedFileTypes[index] === 'folder' ? '📁' : '🎵')
- .fontSize(20)
- Column({ space: 2 }) {
- Text(fileName)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- if (this.selectedFileTypes[index] === 'folder') {
- Text('文件夹')
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- }
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- Button({ type: ButtonType.Normal }) {
- Text('移除')
- .fontSize(12)
- .fontWeight(FontWeight.Medium)
- }
- .height(32)
- .padding({ left: 12, right: 12 })
- .backgroundColor(this.isDarkMode ? '#D94838' : '#FF3B30')
- .fontColor('#FFFFFF')
- .borderRadius(6)
- .onClick(() => {
- this.removeSelectedFile(index);
- })
- }
- .width('100%')
- .padding(12)
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#F5F7FA')
- .borderRadius(8)
- .border({
- width: 1,
- color: this.isDarkMode ? '#19FFFFFF' : '#E5E5EA'
- })
- }
- }, (fileName: string, index: number) => `${index}-${fileName}`)
- }
- .width('100%')
- .constraintSize({ maxHeight: 300 })
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- .borderRadius(12)
- .shadow({
- radius: this.isDarkMode ? 8 : 12,
- color: this.isDarkMode ? '#0C000000' : '#19000000',
- offsetX: 0,
- offsetY: 2
- })
- .margin({ bottom: 16 })
- }
- build() {
- Column() {
- // 标题栏
- this.topTitleBar()
- // 页面内容区域
- Scroll() {
- Column({ space: 0 }) {
- // 账户选择区域
- // this.AccountSelectorSection()
- // 上传配置区域
- this.UploadConfigSection()
- // 文件选择区域
- this.FilePickerSection()
- // 已选文件列表
- if (this.selectedFiles.length > 0) {
- this.SelectedFilesList()
- }
- // 开始上传按钮
- if (!this.isUploading) {
- this.StartUploadButton()
- }
- // 上传进度显示
- this.UploadProgressView()
- // 上传队列管理
- if (this.uploadQueue.length > 0 || this.finishQueue.length > 0) {
- this.UploadQueueView()
- }
- }
- .width('100%')
- .constraintSize({ minHeight: '100%' })
- }
- .layoutWeight(1)
- .width('100%')
- .padding(16)
- .scrollBar(BarState.Auto)
- // 底部安全区
- Row()
- .height(this.bottomSafeHeight)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- }
- .width('100%')
- .height('100%')
- .backgroundColor(this.isDarkMode ? '#191A1C' : '#F1F3F5')
- .bindSheet($$this.isShowFileBrowser, this.FileBrowserDialog(), {
- height: '95%',
- dragBar: true,
- showClose: true,
- preferType: SheetType.BOTTOM,
- blurStyle: BlurStyle.Thin,
- title: { title: '浏览选择文件' }
- })
- }
- @Builder
- topTitleBar() {
- Column() {
- Row({ space: 15 }) {
- //左侧滑动按钮
- Button({ type: ButtonType.Circle, stateEffect: true }) {
- SymbolGlyph($r('sys.symbol.chevron_left'))
- .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().getRouter().back();
- })
- .attributeModifier(new ShadowModifier())
- .zIndex(0)
- Text('上传音乐')
- .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 })
- }
- }
- .padding({
- top: this.topSafeHeight + 10,
- left: 10,
- right: 10,
- bottom: 12
- })
- .width('100%')
- }
- /**
- * 文件浏览器对话框
- */
- @Builder
- FileBrowserDialog() {
- Column() {
- // 当前路径和返回按钮
- Row({ space: 12 }) {
- Column({ space: 4 }) {
- Text('当前目录')
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- Text(this.fileBrowserPath)
- .fontSize(13)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- if (this.fileBrowserPath.lastIndexOf('/') > 0) {
- Button('返回上级')
- .fontSize(12)
- .height(36)
- .padding({ left: 12, right: 12 })
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(8)
- .onClick(() => {
- this.goBackDirectory();
- })
- }
- }
- .width('100%')
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#191A1C' : '#F1F3F5')
- // 文件列表
- if (this.isLoadingFiles) {
- Column({ space: 12 }) {
- LoadingProgress()
- .width(48)
- .height(48)
- .color(this.themeColor)
- Text('加载中...')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#8E8E93')
- }
- .width('100%')
- .height(400)
- .justifyContent(FlexAlign.Center)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- } else if (this.browserFiles.length === 0) {
- Column({ space: 12 }) {
- Text('📂')
- .fontSize(48)
- Text('当前目录为空')
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- }
- .width('100%')
- .height(400)
- .justifyContent(FlexAlign.Center)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- } else {
- List({ space: 8 }) {
- ForEach(this.browserFiles, (item: LocalFileItem, index: number) => {
- ListItem() {
- Row({ space: 12 }) {
- // 复选框
- Checkbox()
- .select(this.selectedBrowserItems.has(item.path))
- .selectedColor(this.themeColor)
- .onChange((checked: boolean) => {
- this.toggleItemSelection(item);
- })
- // 图标
- Text(item.isDirectory ? '📁' : '🎵')
- .fontSize(24)
- // 文件名
- Column({ space: 2 }) {
- Text(item.name)
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- if (item.isDirectory) {
- Text('文件夹')
- .fontSize(12)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- }
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- // 进入按钮(仅文件夹)
- if (item.isDirectory) {
- Text('→')
- .fontSize(20)
- .fontColor(this.isDarkMode ? '#66FFFFFF' : '#8E8E93')
- .onClick(() => {
- this.enterDirectory(item);
- })
- }
- }
- .width('100%')
- .padding(12)
- .backgroundColor(this.selectedBrowserItems.has(item.path) ?
- (this.isDarkMode ? '#2E3238' : '#FFFFFF') :
- (this.isDarkMode ? '#2E3238' : '#F5F7FA'))
- .borderRadius(8)
- .border({
- width: this.selectedBrowserItems.has(item.path) ? 2 : 1,
- color: this.selectedBrowserItems.has(item.path) ?
- this.themeColor :
- (this.isDarkMode ? '#19FFFFFF' : '#E5E5EA')
- })
- .onClick(() => {
- if (!item.isDirectory) {
- this.toggleItemSelection(item);
- }
- })
- }
- }, (item: LocalFileItem, index: number) => `${index}-${item.path}`)
- }
- .width('100%')
- .layoutWeight(1)
- .padding(16)
- .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
- }
- // 底部按钮
- Row({ space: 12 }) {
- Text(`已选择 ${this.selectedBrowserItems.size} 项`)
- .fontSize(14)
- .fontColor(this.isDarkMode ? '#99FFFFFF' : '#8E8E93')
- .layoutWeight(1)
- Button('取消')
- .fontSize(14)
- .height(48)
- .padding({ left: 30, right: 30 })
- .backgroundColor(this.isDarkMode ? '#2E3238' : '#E5E5EA')
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
- .borderRadius(8)
- .onClick(() => {
- this.closeFileBrowser();
- })
- Button('确定')
- .fontSize(14)
- .height(48)
- .padding({ left: 30, right: 30 })
- .backgroundColor(this.themeColor)
- .fontColor('#FFFFFF')
- .borderRadius(8)
- .enabled(this.selectedBrowserItems.size > 0)
- .opacity(this.selectedBrowserItems.size > 0 ? 1.0 : 0.4)
- .onClick(() => {
- this.confirmFileSelection();
- })
- }
- .width('100%')
- .padding({ left:16,right:16 })
- .margin({bottom:12})
- }
- }
- }
|