|
@@ -7,13 +7,26 @@ import Logger from '../common/util/Logger';
|
|
|
import { router } from '@kit.ArkUI';
|
|
import { router } from '@kit.ArkUI';
|
|
|
import { CommonConstants } from '../common/constants/CommonConstants';
|
|
import { CommonConstants } from '../common/constants/CommonConstants';
|
|
|
import { Constants } from '../Constants';
|
|
import { Constants } from '../Constants';
|
|
|
-import { picker } from '@kit.CoreFileKit';
|
|
|
|
|
|
|
+import { picker, fileUri } from '@kit.CoreFileKit';
|
|
|
import { BusinessError } from '@kit.BasicServicesKit';
|
|
import { BusinessError } from '@kit.BasicServicesKit';
|
|
|
-import { fileUri } from '@kit.CoreFileKit';
|
|
|
|
|
-import { PreferencesUtil } from '@pura/harmony-utils';
|
|
|
|
|
|
|
+import { PreferencesUtil, AppUtil } from '@pura/harmony-utils';
|
|
|
import { UploadTaskDataSource } from '../viewmodel/UploadTask';
|
|
import { UploadTaskDataSource } from '../viewmodel/UploadTask';
|
|
|
|
|
+import { promptAction } from '@kit.ArkUI';
|
|
|
|
|
+import fs from '@ohos.file.fs';
|
|
|
|
|
|
|
|
-const TAG = 'UploadMusicPage';
|
|
|
|
|
|
|
+const TAG = 'heanup UploadMusicPage';
|
|
|
|
|
+
|
|
|
|
|
+interface LocalFileItem {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ path: string;
|
|
|
|
|
+ isDirectory: boolean;
|
|
|
|
|
+ isAudioFile: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface LocalAudioFile {
|
|
|
|
|
+ fullPath: string;
|
|
|
|
|
+ relativePath: string;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
@Entry
|
|
@Entry
|
|
|
@Component
|
|
@Component
|
|
@@ -26,7 +39,15 @@ export struct UploadMusicPage {
|
|
|
// 页面状态变量
|
|
// 页面状态变量
|
|
|
@State selectedFiles: string[] = [];
|
|
@State selectedFiles: string[] = [];
|
|
|
@State selectedFileNames: string[] = [];
|
|
@State selectedFileNames: string[] = [];
|
|
|
|
|
+ @State selectedFileTypes: string[] = []; // 'file' 或 'folder'
|
|
|
@State accounts: WebDavAccount[] = [];
|
|
@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 selectedAccount: WebDavAccount | null = null;
|
|
|
@State uploadPath: string = '/';
|
|
@State uploadPath: string = '/';
|
|
|
@State availablePaths: string[] = [];
|
|
@State availablePaths: string[] = [];
|
|
@@ -57,6 +78,7 @@ export struct UploadMusicPage {
|
|
|
private eventHandler: (event: string) => void = (event: string) => {
|
|
private eventHandler: (event: string) => void = (event: string) => {
|
|
|
this.handleUploadEvent(event);
|
|
this.handleUploadEvent(event);
|
|
|
};
|
|
};
|
|
|
|
|
+ private cachedDownloadRoot: string = '';
|
|
|
|
|
|
|
|
aboutToAppear(): void {
|
|
aboutToAppear(): void {
|
|
|
Logger.info(TAG, '页面即将显示');
|
|
Logger.info(TAG, '页面即将显示');
|
|
@@ -118,6 +140,88 @@ export struct UploadMusicPage {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ 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}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ const packName = AppUtil.getBundleName();
|
|
|
|
|
+ this.cachedDownloadRoot = `/storage/Users/currentUser/Download/${packName}`;
|
|
|
|
|
+ return this.cachedDownloadRoot;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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(): 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);
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 预选指定账户
|
|
* 预选指定账户
|
|
|
* @param accountId 账户ID
|
|
* @param accountId 账户ID
|
|
@@ -318,16 +422,19 @@ export struct UploadMusicPage {
|
|
|
|
|
|
|
|
// 提取文件名用于显示
|
|
// 提取文件名用于显示
|
|
|
this.selectedFileNames = [];
|
|
this.selectedFileNames = [];
|
|
|
|
|
+ this.selectedFileTypes = [];
|
|
|
for (let i = 0; i < documentSelectResult.length; i++) {
|
|
for (let i = 0; i < documentSelectResult.length; i++) {
|
|
|
const uri = documentSelectResult[i];
|
|
const uri = documentSelectResult[i];
|
|
|
try {
|
|
try {
|
|
|
const fileUriObj = new fileUri.FileUri(uri);
|
|
const fileUriObj = new fileUri.FileUri(uri);
|
|
|
const fileName = fileUriObj.name;
|
|
const fileName = fileUriObj.name;
|
|
|
this.selectedFileNames.push(fileName);
|
|
this.selectedFileNames.push(fileName);
|
|
|
|
|
+ this.selectedFileTypes.push('file');
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
const err = error as Error;
|
|
const err = error as Error;
|
|
|
Logger.error(TAG, `解析文件URI失败: ${err.message}`);
|
|
Logger.error(TAG, `解析文件URI失败: ${err.message}`);
|
|
|
this.selectedFileNames.push('未知文件');
|
|
this.selectedFileNames.push('未知文件');
|
|
|
|
|
+ this.selectedFileTypes.push('file');
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -339,6 +446,218 @@ export struct UploadMusicPage {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 打开文件浏览器
|
|
|
|
|
+ */
|
|
|
|
|
+ private async openFileBrowser(): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const initialPath = await this.resolveInitialBrowserPath();
|
|
|
|
|
+ if (!initialPath) {
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: '无法定位到下载目录,请授予文件访问权限',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.fileBrowserPath = initialPath;
|
|
|
|
|
+ this.selectedBrowserItems.clear();
|
|
|
|
|
+ this.isShowFileBrowser = true;
|
|
|
|
|
+ await this.loadLocalFiles(initialPath);
|
|
|
|
|
+ Logger.info(TAG, `文件浏览器已定位到: ${initialPath}`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const err = error as BusinessError;
|
|
|
|
|
+ Logger.error(TAG, `打开文件浏览器失败: ${err.message}`);
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: '打开文件浏览器失败',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 加载本地文件列表
|
|
|
|
|
+ */
|
|
|
|
|
+ private async loadLocalFiles(path: string): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.isLoadingFiles = true;
|
|
|
|
|
+ this.browserFiles = [];
|
|
|
|
|
+
|
|
|
|
|
+ Logger.info(TAG, `加载目录: ${path}`);
|
|
|
|
|
+
|
|
|
|
|
+ const files = fs.listFileSync(path);
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
|
|
+ const fileName = files[i];
|
|
|
|
|
+ const fullPath = `${path}/${fileName}`;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const stat = fs.statSync(fullPath);
|
|
|
|
|
+ const isDirectory = stat.isDirectory();
|
|
|
|
|
+ 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) {
|
|
|
|
|
+ this.fileBrowserPath = item.path;
|
|
|
|
|
+ await this.loadLocalFiles(item.path);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 返回上级目录
|
|
|
|
|
+ */
|
|
|
|
|
+ private async goBackDirectory(): Promise<void> {
|
|
|
|
|
+ const lastSlash = this.fileBrowserPath.lastIndexOf('/');
|
|
|
|
|
+ if (lastSlash > 0) {
|
|
|
|
|
+ this.fileBrowserPath = this.fileBrowserPath.substring(0, lastSlash);
|
|
|
|
|
+ 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 confirmFileSelection(): void {
|
|
|
|
|
+ if (this.selectedBrowserItems.size === 0) {
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: '请至少选择一个项目',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 将选中的项目添加到已选列表
|
|
|
|
|
+ this.selectedBrowserItems.forEach(path => {
|
|
|
|
|
+ const item = this.browserFiles.find(f => f.path === path);
|
|
|
|
|
+ if (item) {
|
|
|
|
|
+ const uri = `file://${path}`;
|
|
|
|
|
+ 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 = [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 递归扫描文件夹,获取所有音频文件
|
|
|
|
|
+ */
|
|
|
|
|
+ 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 文件索引
|
|
* @param index 文件索引
|
|
@@ -347,7 +666,8 @@ export struct UploadMusicPage {
|
|
|
if (index >= 0 && index < this.selectedFiles.length) {
|
|
if (index >= 0 && index < this.selectedFiles.length) {
|
|
|
this.selectedFiles.splice(index, 1);
|
|
this.selectedFiles.splice(index, 1);
|
|
|
this.selectedFileNames.splice(index, 1);
|
|
this.selectedFileNames.splice(index, 1);
|
|
|
- Logger.info(TAG, `移除文件,剩余 ${this.selectedFiles.length} 个`);
|
|
|
|
|
|
|
+ this.selectedFileTypes.splice(index, 1);
|
|
|
|
|
+ Logger.info(TAG, `移除项目,剩余 ${this.selectedFiles.length} 个`);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -370,8 +690,16 @@ export struct UploadMusicPage {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ Logger.info(TAG, '========== 打开路径浏览器 ==========');
|
|
|
|
|
+ Logger.info(TAG, `当前上传路径: ${this.uploadPath}`);
|
|
|
|
|
+ Logger.info(TAG, `选中账户: ${this.selectedAccount.name}`);
|
|
|
|
|
+
|
|
|
this.currentBrowsePath = this.uploadPath;
|
|
this.currentBrowsePath = this.uploadPath;
|
|
|
this.isShowPathBrowser = true;
|
|
this.isShowPathBrowser = true;
|
|
|
|
|
+
|
|
|
|
|
+ Logger.info(TAG, `isShowPathBrowser 设置为: ${this.isShowPathBrowser}`);
|
|
|
|
|
+ Logger.info(TAG, '开始加载文件夹列表...');
|
|
|
|
|
+
|
|
|
this.loadWebDavFolders(this.currentBrowsePath);
|
|
this.loadWebDavFolders(this.currentBrowsePath);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -379,6 +707,7 @@ export struct UploadMusicPage {
|
|
|
* 关闭路径浏览器
|
|
* 关闭路径浏览器
|
|
|
*/
|
|
*/
|
|
|
private closePathBrowser(): void {
|
|
private closePathBrowser(): void {
|
|
|
|
|
+ Logger.info(TAG, '========== 关闭路径浏览器 ==========');
|
|
|
this.isShowPathBrowser = false;
|
|
this.isShowPathBrowser = false;
|
|
|
this.browseFolders = [];
|
|
this.browseFolders = [];
|
|
|
}
|
|
}
|
|
@@ -475,54 +804,103 @@ export struct UploadMusicPage {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
Logger.info(TAG, '开始上传任务');
|
|
Logger.info(TAG, '开始上传任务');
|
|
|
|
|
+
|
|
|
|
|
+ // 显示处理提示
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: '正在处理文件...',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// 构建VideoItem列表
|
|
// 构建VideoItem列表
|
|
|
const songs: VideoItem[] = [];
|
|
const songs: VideoItem[] = [];
|
|
|
|
|
+ let totalFiles = 0;
|
|
|
|
|
+ let totalFolders = 0;
|
|
|
|
|
+
|
|
|
for (let i = 0; i < this.selectedFiles.length; i++) {
|
|
for (let i = 0; i < this.selectedFiles.length; i++) {
|
|
|
const selectedUri = this.selectedFiles[i];
|
|
const selectedUri = this.selectedFiles[i];
|
|
|
- let displayName = this.selectedFileNames[i];
|
|
|
|
|
- let resolvedPath = '';
|
|
|
|
|
|
|
+ const displayName = this.selectedFileNames[i];
|
|
|
|
|
+ const itemType = this.selectedFileTypes[i];
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const fileUriObj = new fileUri.FileUri(selectedUri);
|
|
const fileUriObj = new fileUri.FileUri(selectedUri);
|
|
|
- resolvedPath = fileUriObj.path || '';
|
|
|
|
|
- if (!displayName) {
|
|
|
|
|
- displayName = fileUriObj.name || 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,
|
|
|
|
|
+ audioFileName
|
|
|
|
|
+ );
|
|
|
|
|
+ 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,
|
|
|
|
|
+ displayName
|
|
|
|
|
+ );
|
|
|
|
|
+ videoItem.remote_rel_path = displayName;
|
|
|
|
|
+ songs.push(videoItem);
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
const err = error as Error;
|
|
const err = error as Error;
|
|
|
- Logger.error(TAG, `解析文件路径失败: ${err.message}`);
|
|
|
|
|
|
|
+ Logger.error(TAG, `处理项目失败: ${err.message}`);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (!resolvedPath) {
|
|
|
|
|
- Logger.error(TAG, `无法获取本地路径,跳过文件: ${selectedUri}`);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const videoItem = new VideoItem(
|
|
|
|
|
- displayName,
|
|
|
|
|
- selectedUri,
|
|
|
|
|
- resolvedPath,
|
|
|
|
|
- CommonConstants.TYPE_LOCAL,
|
|
|
|
|
- 0,
|
|
|
|
|
- '',
|
|
|
|
|
- undefined,
|
|
|
|
|
- undefined,
|
|
|
|
|
- undefined,
|
|
|
|
|
- undefined,
|
|
|
|
|
- undefined,
|
|
|
|
|
- displayName
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- songs.push(videoItem);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (songs.length === 0) {
|
|
if (songs.length === 0) {
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: '未找到可上传的音频文件',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
Logger.warn(TAG, '未找到可上传的有效文件,取消任务');
|
|
Logger.warn(TAG, '未找到可上传的有效文件,取消任务');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ Logger.info(TAG, `处理完成: ${totalFiles} 个文件, ${totalFolders} 个文件夹, 共 ${songs.length} 个音频文件`);
|
|
|
|
|
+
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: `准备上传 ${songs.length} 个文件`,
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// 添加到上传队列,传递用户选择的上传路径
|
|
// 添加到上传队列,传递用户选择的上传路径
|
|
|
Logger.info(TAG, `使用上传路径: ${this.uploadPath}`);
|
|
Logger.info(TAG, `使用上传路径: ${this.uploadPath}`);
|
|
|
this.webdavManager.addToUploadQueue(songs, this.selectedAccount, this.uploadPath);
|
|
this.webdavManager.addToUploadQueue(songs, this.selectedAccount, this.uploadPath);
|
|
@@ -536,6 +914,10 @@ export struct UploadMusicPage {
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
const err = error as Error;
|
|
const err = error as Error;
|
|
|
Logger.error(TAG, `启动上传失败: ${err.message}`);
|
|
Logger.error(TAG, `启动上传失败: ${err.message}`);
|
|
|
|
|
+ promptAction.showToast({
|
|
|
|
|
+ message: `上传失败: ${err.message}`,
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -900,6 +1282,13 @@ export struct UploadMusicPage {
|
|
|
.borderRadius(6)
|
|
.borderRadius(6)
|
|
|
.enabled(this.selectedAccount !== null)
|
|
.enabled(this.selectedAccount !== null)
|
|
|
.opacity(this.selectedAccount !== null ? 1.0 : 0.4)
|
|
.opacity(this.selectedAccount !== null ? 1.0 : 0.4)
|
|
|
|
|
+ .bindContentCover(this.isShowPathBrowser, this.PathBrowserDialogBuilder(), {
|
|
|
|
|
+ modalTransition: ModalTransition.DEFAULT,
|
|
|
|
|
+ onDisappear: () => {
|
|
|
|
|
+ Logger.info(TAG, 'PathBrowser onDisappear 回调');
|
|
|
|
|
+ this.isShowPathBrowser = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
.onClick(() => {
|
|
.onClick(() => {
|
|
|
this.openPathBrowser();
|
|
this.openPathBrowser();
|
|
|
})
|
|
})
|
|
@@ -1375,29 +1764,57 @@ export struct UploadMusicPage {
|
|
|
}
|
|
}
|
|
|
.alignSelf(ItemAlign.Start)
|
|
.alignSelf(ItemAlign.Start)
|
|
|
|
|
|
|
|
- Button({ type: ButtonType.Normal }) {
|
|
|
|
|
- Row({ space: 8 }) {
|
|
|
|
|
- Text('📂')
|
|
|
|
|
- .fontSize(18)
|
|
|
|
|
- Text('选择音频文件')
|
|
|
|
|
- .fontSize(15)
|
|
|
|
|
- .fontWeight(FontWeight.Medium)
|
|
|
|
|
|
|
+ // 按钮组
|
|
|
|
|
+ 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%')
|
|
.width('100%')
|
|
|
- .height(50)
|
|
|
|
|
- .backgroundColor(this.themeColor)
|
|
|
|
|
- .fontColor('#FFFFFF')
|
|
|
|
|
- .borderRadius(10)
|
|
|
|
|
- .shadow({
|
|
|
|
|
- radius: 8,
|
|
|
|
|
- color: this.themeColor + '40',
|
|
|
|
|
- offsetX: 0,
|
|
|
|
|
- offsetY: 3
|
|
|
|
|
- })
|
|
|
|
|
- .onClick(() => {
|
|
|
|
|
- this.openFilePicker();
|
|
|
|
|
- })
|
|
|
|
|
|
|
|
|
|
if (this.selectedFiles.length > 0) {
|
|
if (this.selectedFiles.length > 0) {
|
|
|
Row({ space: 10 }) {
|
|
Row({ space: 10 }) {
|
|
@@ -1405,15 +1822,35 @@ export struct UploadMusicPage {
|
|
|
.fontSize(18)
|
|
.fontSize(18)
|
|
|
.fontColor(this.themeColor)
|
|
.fontColor(this.themeColor)
|
|
|
|
|
|
|
|
- Text(`已选择 ${this.selectedFiles.length} 个文件`)
|
|
|
|
|
- .fontSize(14)
|
|
|
|
|
- .fontWeight(FontWeight.Medium)
|
|
|
|
|
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
|
|
|
|
|
|
|
+ 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%')
|
|
.width('100%')
|
|
|
.padding(12)
|
|
.padding(12)
|
|
|
- .backgroundColor(this.isDarkMode ? this.themeColor + '20' : this.themeColor + '10')
|
|
|
|
|
|
|
+ .backgroundColor(this.isDarkMode ? '#2E3238' : '#F5F7FA')
|
|
|
.borderRadius(8)
|
|
.borderRadius(8)
|
|
|
|
|
+ .border({
|
|
|
|
|
+ width: 1,
|
|
|
|
|
+ color: this.isDarkMode ? this.themeColor + '40' : this.themeColor + '30'
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
.width('100%')
|
|
.width('100%')
|
|
@@ -1461,27 +1898,40 @@ export struct UploadMusicPage {
|
|
|
ForEach(this.selectedFileNames, (fileName: string, index: number) => {
|
|
ForEach(this.selectedFileNames, (fileName: string, index: number) => {
|
|
|
ListItem() {
|
|
ListItem() {
|
|
|
Row({ space: 12 }) {
|
|
Row({ space: 12 }) {
|
|
|
- // 文件图标
|
|
|
|
|
- Text('🎵')
|
|
|
|
|
|
|
+ // 文件/文件夹图标
|
|
|
|
|
+ Text(this.selectedFileTypes[index] === 'folder' ? '📁' : '🎵')
|
|
|
.fontSize(20)
|
|
.fontSize(20)
|
|
|
|
|
|
|
|
- Text(fileName)
|
|
|
|
|
- .fontSize(14)
|
|
|
|
|
- .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#E5000000')
|
|
|
|
|
- .layoutWeight(1)
|
|
|
|
|
- .maxLines(1)
|
|
|
|
|
- .textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
|
|
|
|
+ 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('移除')
|
|
|
|
|
- .fontSize(12)
|
|
|
|
|
- .height(32)
|
|
|
|
|
- .padding({ left: 12, right: 12 })
|
|
|
|
|
- .backgroundColor(this.isDarkMode ? '#D94838' : '#FF3B30')
|
|
|
|
|
- .fontColor('#FFFFFF')
|
|
|
|
|
- .borderRadius(6)
|
|
|
|
|
- .onClick(() => {
|
|
|
|
|
- this.removeSelectedFile(index);
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ 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%')
|
|
.width('100%')
|
|
|
.padding(12)
|
|
.padding(12)
|
|
@@ -1496,10 +1946,6 @@ export struct UploadMusicPage {
|
|
|
}
|
|
}
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
.constraintSize({ maxHeight: 300 })
|
|
.constraintSize({ maxHeight: 300 })
|
|
|
- .divider({
|
|
|
|
|
- strokeWidth: 1,
|
|
|
|
|
- color: this.isDarkMode ? '#19FFFFFF' : '#0C000000'
|
|
|
|
|
- })
|
|
|
|
|
}
|
|
}
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
.padding(16)
|
|
.padding(16)
|
|
@@ -1594,27 +2040,264 @@ export struct UploadMusicPage {
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
.height('100%')
|
|
.height('100%')
|
|
|
.backgroundColor(this.isDarkMode ? '#191A1C' : '#F1F3F5')
|
|
.backgroundColor(this.isDarkMode ? '#191A1C' : '#F1F3F5')
|
|
|
- .bindContentCover(this.isShowPathBrowser, this.PathBrowserDialogBuilder(), {
|
|
|
|
|
|
|
+ .bindContentCover(this.isShowFileBrowser, this.FileBrowserDialogBuilder(), {
|
|
|
modalTransition: ModalTransition.DEFAULT,
|
|
modalTransition: ModalTransition.DEFAULT,
|
|
|
- backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
|
|
|
onDisappear: () => {
|
|
onDisappear: () => {
|
|
|
- this.isShowPathBrowser = false;
|
|
|
|
|
|
|
+ Logger.info(TAG, 'FileBrowser onDisappear 回调');
|
|
|
|
|
+ this.isShowFileBrowser = false;
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Builder
|
|
@Builder
|
|
|
PathBrowserDialogBuilder() {
|
|
PathBrowserDialogBuilder() {
|
|
|
- Column() {
|
|
|
|
|
- this.PathBrowserDialog()
|
|
|
|
|
|
|
+ Stack() {
|
|
|
|
|
+ // 背景层
|
|
|
|
|
+ Column()
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height('100%')
|
|
|
|
|
+ .backgroundColor('rgba(0, 0, 0, 0.5)')
|
|
|
|
|
+
|
|
|
|
|
+ // 对话框内容(居中显示)
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ this.PathBrowserDialog()
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height('100%')
|
|
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
|
|
+ .alignItems(HorizontalAlign.Center)
|
|
|
}
|
|
}
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
.height('100%')
|
|
.height('100%')
|
|
|
- .justifyContent(FlexAlign.Center)
|
|
|
|
|
- .backgroundColor('rgba(0, 0, 0, 0.5)')
|
|
|
|
|
- .onClick(() => {
|
|
|
|
|
- // 点击背景关闭对话框
|
|
|
|
|
- this.closePathBrowser();
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 文件浏览器对话框
|
|
|
|
|
+ */
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ FileBrowserDialog() {
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ // 标题栏
|
|
|
|
|
+ Row({ space: 12 }) {
|
|
|
|
|
+ Text('浏览选择文件')
|
|
|
|
|
+ .fontSize(18)
|
|
|
|
|
+ .fontWeight(FontWeight.Bold)
|
|
|
|
|
+ .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
|
|
|
|
|
+ .layoutWeight(1)
|
|
|
|
|
+
|
|
|
|
|
+ Button('关闭')
|
|
|
|
|
+ .fontSize(14)
|
|
|
|
|
+ .height(36)
|
|
|
|
|
+ .padding({ left: 16, right: 16 })
|
|
|
|
|
+ .backgroundColor(this.isDarkMode ? '#2E3238' : '#E5E5EA')
|
|
|
|
|
+ .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
|
|
|
|
|
+ .borderRadius(8)
|
|
|
|
|
+ .onClick(() => {
|
|
|
|
|
+ this.closeFileBrowser();
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .padding(16)
|
|
|
|
|
+ .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
|
|
|
|
|
+
|
|
|
|
|
+ // 当前路径和返回按钮
|
|
|
|
|
+ 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%')
|
|
|
|
|
+ .height(400)
|
|
|
|
|
+ .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: 20, right: 20 })
|
|
|
|
|
+ .backgroundColor(this.isDarkMode ? '#2E3238' : '#E5E5EA')
|
|
|
|
|
+ .fontColor(this.isDarkMode ? '#E5FFFFFF' : '#1F1F1F')
|
|
|
|
|
+ .borderRadius(8)
|
|
|
|
|
+ .onClick(() => {
|
|
|
|
|
+ this.closeFileBrowser();
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ Button('确定')
|
|
|
|
|
+ .fontSize(14)
|
|
|
|
|
+ .height(48)
|
|
|
|
|
+ .padding({ left: 20, right: 20 })
|
|
|
|
|
+ .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(16)
|
|
|
|
|
+ .backgroundColor(this.isDarkMode ? '#23272E' : '#FFFFFF')
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('90%')
|
|
|
|
|
+ .constraintSize({ maxWidth: 700, maxHeight: '80%' })
|
|
|
|
|
+ .backgroundColor(this.isDarkMode ? '#191A1C' : '#FFFFFF')
|
|
|
|
|
+ .borderRadius(16)
|
|
|
|
|
+ .shadow({
|
|
|
|
|
+ radius: 24,
|
|
|
|
|
+ color: this.isDarkMode ? '#33000000' : '#19000000',
|
|
|
|
|
+ offsetX: 0,
|
|
|
|
|
+ offsetY: 8
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ FileBrowserDialogBuilder() {
|
|
|
|
|
+ Stack() {
|
|
|
|
|
+ // 背景层(不添加 onClick,避免误关闭)
|
|
|
|
|
+ Column()
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height('100%')
|
|
|
|
|
+ .backgroundColor('rgba(0, 0, 0, 0.5)')
|
|
|
|
|
+
|
|
|
|
|
+ // 对话框内容(居中显示)
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ this.FileBrowserDialog()
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height('100%')
|
|
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height('100%')
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|