| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364 |
- 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;
- }
- @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 uploadReceivedSize: number = 0;
- @State totalSize: number = 0;
- @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;
- // 任务队列Tab选择
- @State @Watch('onQueueTabChange') selectedQueueIndex: number[] = [0];
- // 上传速度计算相关变量
- private lastUploadTime: number = 0;
- private lastUploadedSize: 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 = '';
- //以下是组件的参数
- @Prop accountId: number
- @Link isShowUploadFile: boolean
- onResult = (result:boolean) => {
- }
- // 重复文件处理方式变化监听
- onDuplicateActionChange() {
- if (this.selectedDuplicateIndex.length > 0) {
- const actionMap = ['skip', 'overwrite', 'rename'];
- this.duplicateAction = actionMap[this.selectedDuplicateIndex[0]];
- Logger.info(TAG, `重复文件处理方式变更为: ${this.duplicateAction}`);
- }
- }
- // 任务队列Tab变化监听
- onQueueTabChange() {
- // 可以在这里添加Tab变化的逻辑
- }
- // 获取当前重复文件处理方式的描述
- 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);
- // }
- this.preselectAccount(this.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;
- this.lastUploadTime = Date.now();
- this.lastUploadedSize = 0;
- Logger.info(TAG, '上传开始');
- }
- /**
- * 处理上传进度更新事件
- */
- private handleUploadProgress(): void {
- const uploaded = this.webdavManager.uploadReceivedSize;
- const total = this.webdavManager.uploadTotalSize;
- this.uploadReceivedSize = uploaded;
- this.totalSize = total;
- if (total > 0) {
- this.uploadProgress = Math.floor((uploaded / total) * 100);
- // 计算上传速度(基于时间差计算)
- const currentTime = Date.now();
- if (this.lastUploadTime > 0) {
- const timeDiff = (currentTime - this.lastUploadTime) / 1000; // 转换为秒
- const sizeDiff = uploaded - this.lastUploadedSize; // 字节差
- if (timeDiff > 0 && sizeDiff >= 0) {
- const speedBytesPerSecond = sizeDiff / timeDiff;
- if (speedBytesPerSecond < 1024) {
- this.uploadSpeed = `${speedBytesPerSecond.toFixed(2)} B/s`;
- } else if (speedBytesPerSecond < 1024 * 1024) {
- this.uploadSpeed = `${(speedBytesPerSecond / 1024).toFixed(2)} KB/s`;
- } else {
- this.uploadSpeed = `${(speedBytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
- }
- }
- }
- // 更新上次记录的时间和大小
- this.lastUploadTime = currentTime;
- this.lastUploadedSize = uploaded;
- }
- Logger.info(TAG, `上传进度: ${this.uploadProgress}%`);
- }
- /**
- * 处理上传成功事件
- */
- private handleUploadSuccess(): void {
- this.uploadedCount++;
- Logger.info(TAG, `上传成功,已完成: ${this.uploadedCount}/${this.totalUploadCount}`);
- this.onResult(true)
- }
- /**
- * 处理上传失败事件
- */
- 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 sizeInBytes 文件大小(字节)
- * @returns 格式化后的文件大小字符串
- */
- private formatFileSize(sizeInBytes: number): string {
- if (sizeInBytes < 1024) {
- return `${sizeInBytes} B`;
- } else if (sizeInBytes < 1024 * 1024) {
- return `${(sizeInBytes / 1024).toFixed(2)} KB`;
- } else if (sizeInBytes < 1024 * 1024 * 1024) {
- return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;
- } else {
- return `${(sizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
- }
- }
- /**
- * 移除已选文件
- * @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 }) {
- SymbolGlyph($r('sys.symbol.text_and_arrow_up'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- Text('上传中')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- // 当前文件信息卡片
- Column({ space: 10 }) {
- Row({ space: 8 }) {
- SymbolGlyph($r('sys.symbol.doc_text'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- 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($r('app.color.text_color'))
- .opacity(0.8)
- .textAlign(TextAlign.End)
- Blank()
- Text(`${this.formatFileSize(this.uploadReceivedSize)} / ${this.formatFileSize(this.totalSize)}`)
- .fontSize(12)
- .fontColor($r('app.color.text_color'))
- .opacity(0.8)
- .textAlign(TextAlign.End)
- }
- .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 }) {
- SymbolGlyph($r('sys.symbol.ranking'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- Text('任务队列')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- // 任务队列SegmentButton
- Column({ space: 12 }) {
- SegmentButton({
- options: SegmentButtonOptions.capsule({
- buttons: [
- { 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
- }),
- selectedIndexes: $selectedQueueIndex
- })
- .width('100%')
- .margin({ bottom: 12 })
- // 内容区域
- if (this.selectedQueueIndex[0] === 0) {
- this.QueueList(this.uploadQueueDataSource, 'pending')
- } else {
- this.QueueList(this.finishQueueDataSource, 'finished')
- }
- }
- .height(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
- })
- .visibility(Visibility.None)
- .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 }) {
- // 文件图标
- SymbolGlyph($r('sys.symbol.music'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- 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 {
- SymbolGlyph($r('sys.symbol.checkmark_circle'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- }
- }
- .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 }) {
- SymbolGlyph($r('sys.symbol.arrow_up_to_line'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- Text('开始上传')
- .fontSize(16)
- .fontWeight(FontWeight.Bold)
- }
- }
- .width('100%')
- .height(54)
- .backgroundColor($r('app.color.index_background'))
- .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.9)
- .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 }) {
- SymbolGlyph($r('sys.symbol.folder'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- 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(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 }) {
- SymbolGlyph($r('sys.symbol.doc_text'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- Text('选择文件')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
- }
- .alignSelf(ItemAlign.Start)
- // 按钮组
- Row({ space: 10 }) {
- Button({ type: ButtonType.Normal }) {
- Row({ space: 6 }) {
- SymbolGlyph($r('sys.symbol.folder'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- Text('从文件管理器')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- }
- }
- .layoutWeight(1)
- .height(48)
- .backgroundColor($r('app.color.index_background'))
- .borderRadius(10)
- .shadow({
- radius: 8,
- color: this.themeColor + '40',
- offsetX: 0,
- offsetY: 3
- })
- .onClick(() => {
- this.openFilePicker();
- })
- Button({ type: ButtonType.Normal }) {
- Row({ space: 6 }) {
- SymbolGlyph($r('sys.symbol.music'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- Text('从本地目录')
- .fontSize(14)
- .fontWeight(FontWeight.Medium)
- }
- }
- .layoutWeight(1)
- .height(48)
- .backgroundColor($r('app.color.index_background'))
- .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 }) {
- SymbolGlyph($r('sys.symbol.doc'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- 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 }) {
- // 文件/文件夹图标
- SymbolGlyph(this.selectedFileTypes[index] === 'folder' ?
- $r('sys.symbol.folder'):$r('sys.symbol.music'))
- .fontSize(20)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- 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();
- this.getUIContext().animateTo({ duration: 666 }, () => {
- // 动画闭包内控制Image组件的出现和消失
- this.isShowUploadFile = !this.isShowUploadFile
- })
- })
- .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})
- }
- }
- }
|