|
|
@@ -1,4 +1,4 @@
|
|
|
-import { StrUtil, ToastUtil } from '@pura/harmony-utils';
|
|
|
+import { ToastUtil } from '@pura/harmony-utils';
|
|
|
import { CommonConstants } from '../common/constants/CommonConstants';
|
|
|
import { RemoteDriveType } from '../common/enums/RemoteDriveType';
|
|
|
import { WebDavAccount } from '../viewmodel/WebDavAccount';
|
|
|
@@ -6,6 +6,15 @@ import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
|
|
|
import Logger from '../common/util/Logger';
|
|
|
import { ConfigurationConstant, Context } from '@kit.AbilityKit';
|
|
|
|
|
|
+interface ParsedConnectionParts {
|
|
|
+ protocol: string;
|
|
|
+ username?: string;
|
|
|
+ password?: string;
|
|
|
+ host: string;
|
|
|
+ port?: number;
|
|
|
+ path?: string;
|
|
|
+}
|
|
|
+
|
|
|
// 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
|
|
|
function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
|
|
|
if (isDarkMode) {
|
|
|
@@ -19,16 +28,16 @@ function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: bool
|
|
|
return `rgba(${r},${g},${b},${alpha})`;
|
|
|
}
|
|
|
|
|
|
-// WebDAV账户对话框
|
|
|
+// 网盘账户对话框
|
|
|
@Component
|
|
|
-export struct WebDavAccountDialog {
|
|
|
+export struct RemoteDriveAccountDialog {
|
|
|
@Prop isEditMode: boolean = false;
|
|
|
@Prop account: WebDavAccount;
|
|
|
onConfirm?: (account: WebDavAccount) => void;
|
|
|
onCancel?: () => void;
|
|
|
- @State accountName: string = 'demo';
|
|
|
+ @State accountName: string = '新建WebDAV';
|
|
|
@State host: string = '';
|
|
|
- @State port: number = 5005;
|
|
|
+ @State port: number = -1;
|
|
|
@State filepath: string = '/';
|
|
|
@State username: string = '';
|
|
|
@State password: string = '';
|
|
|
@@ -42,6 +51,10 @@ export struct WebDavAccountDialog {
|
|
|
@StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
|
|
|
ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
|
|
|
|
|
|
+ private nameCustomized: boolean = false;
|
|
|
+ private portCustomized: boolean = false;
|
|
|
+ private suppressPortChange: boolean = false;
|
|
|
+
|
|
|
onColorModeChange() {
|
|
|
this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
|
|
|
}
|
|
|
@@ -53,7 +66,7 @@ export struct WebDavAccountDialog {
|
|
|
if(this.isEditMode){
|
|
|
this.accountName = this.account.name;
|
|
|
this.host = this.account.host;
|
|
|
- this.port = this.account.port;
|
|
|
+ this.updatePortState(this.account.port, true);
|
|
|
this.filepath = this.account.filepath;
|
|
|
this.username = this.account.account;
|
|
|
this.password = this.account.password;
|
|
|
@@ -62,10 +75,13 @@ export struct WebDavAccountDialog {
|
|
|
this.shareName = this.account.smbShare ?? '';
|
|
|
this.domain = this.account.smbDomain ?? '';
|
|
|
this.coverPath = this.account.coverPath || '';
|
|
|
- Logger.info('heanup WebDavAccountDialog', `编辑模式加载账户: ${this.accountName}, 封面路径: ${this.coverPath}`);
|
|
|
+ this.nameCustomized = true;
|
|
|
+ Logger.info('heanup RemoteDriveAccountDialog', `编辑模式加载账户: ${this.accountName}, 封面路径: ${this.coverPath}`);
|
|
|
if (this.coverPath) {
|
|
|
- Logger.info('heanup WebDavAccountDialog', `封面文件是否存在: ${ImagePickerUtil.imageExists(this.coverPath)}`);
|
|
|
+ Logger.info('heanup RemoteDriveAccountDialog', `封面文件是否存在: ${ImagePickerUtil.imageExists(this.coverPath)}`);
|
|
|
}
|
|
|
+ } else {
|
|
|
+ this.applyTypeDefaults(this.driveType);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -170,6 +186,7 @@ export struct WebDavAccountDialog {
|
|
|
.maxLines(1)
|
|
|
.onChange((value: string) => {
|
|
|
this.accountName = value;
|
|
|
+ this.nameCustomized = true;
|
|
|
});
|
|
|
}
|
|
|
.width('100%')
|
|
|
@@ -177,17 +194,22 @@ export struct WebDavAccountDialog {
|
|
|
|
|
|
// 服务器地址
|
|
|
Row({ space: 8 }) {
|
|
|
- Text('服务器')
|
|
|
+ Text(this.getServerLabel())
|
|
|
.fontSize(14)
|
|
|
.maxLines(2)
|
|
|
.fontColor($r('app.color.index_tab_font_color'));
|
|
|
- TextArea({ placeholder: '例如: example.com', text: this.host })
|
|
|
+ TextArea({ placeholder: '服务器地址或连接串', text: this.host })
|
|
|
.layoutWeight(1)
|
|
|
.onChange((value: string) => {
|
|
|
- this.host = value;
|
|
|
+ const trimmed = value.trim();
|
|
|
+ if (trimmed.includes('://') && this.tryParseConnectionString(trimmed)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.host = trimmed;
|
|
|
});
|
|
|
}
|
|
|
.alignItems(VerticalAlign.Center);
|
|
|
+ this.buildHelperText(this.getServerHint())
|
|
|
|
|
|
// 端口
|
|
|
Row({ space: 8 }) {
|
|
|
@@ -199,15 +221,21 @@ export struct WebDavAccountDialog {
|
|
|
.maxLines(1)
|
|
|
.type(InputType.Number)
|
|
|
.onChange((value: string) => {
|
|
|
+ if (this.suppressPortChange) {
|
|
|
+ this.suppressPortChange = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
const parsed = parseInt(value);
|
|
|
- if (Number.isNaN(parsed)) {
|
|
|
- this.port = this.driveType === RemoteDriveType.Smb ? 445 : (this.enableHttps ? 443 : 80);
|
|
|
+ const defaultPort = this.driveType === RemoteDriveType.Smb ? 445 : (this.enableHttps ? 443 : 5005);
|
|
|
+ if (Number.isNaN(parsed) || parsed === -1) {
|
|
|
+ this.updatePortState(defaultPort, false);
|
|
|
} else {
|
|
|
- this.port = parsed;
|
|
|
+ this.updatePortState(parsed, true);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
.alignItems(VerticalAlign.Center);
|
|
|
+ this.buildHelperText('若留空将自动使用协议默认端口')
|
|
|
|
|
|
if (this.driveType === RemoteDriveType.Smb) {
|
|
|
// SMB 共享名称
|
|
|
@@ -215,7 +243,7 @@ export struct WebDavAccountDialog {
|
|
|
Text('共享名称')
|
|
|
.fontSize(14)
|
|
|
.fontColor($r('app.color.index_tab_font_color'));
|
|
|
- TextInput({ placeholder: '例如: music', text: this.shareName })
|
|
|
+ TextInput({ placeholder: '共享名称', text: this.shareName })
|
|
|
.layoutWeight(1)
|
|
|
.maxLines(1)
|
|
|
.onChange((value: string) => {
|
|
|
@@ -223,12 +251,13 @@ export struct WebDavAccountDialog {
|
|
|
});
|
|
|
}
|
|
|
.alignItems(VerticalAlign.Center);
|
|
|
+ this.buildHelperText('NAS 上共享根目录的名称,例如 music、share')
|
|
|
|
|
|
Row({ space: 8 }) {
|
|
|
Text('域/工作组')
|
|
|
.fontSize(14)
|
|
|
.fontColor($r('app.color.index_tab_font_color'));
|
|
|
- TextInput({ placeholder: '可选,例如: WORKGROUP', text: this.domain })
|
|
|
+ TextInput({ placeholder: '可选项', text: this.domain })
|
|
|
.layoutWeight(1)
|
|
|
.maxLines(1)
|
|
|
.onChange((value: string) => {
|
|
|
@@ -236,6 +265,7 @@ export struct WebDavAccountDialog {
|
|
|
});
|
|
|
}
|
|
|
.alignItems(VerticalAlign.Center);
|
|
|
+ this.buildHelperText('可选,用于需要域/工作组认证的 SMB 服务器')
|
|
|
}
|
|
|
|
|
|
// 文件目录
|
|
|
@@ -243,7 +273,7 @@ export struct WebDavAccountDialog {
|
|
|
Text('文件目录')
|
|
|
.fontSize(14)
|
|
|
.fontColor($r('app.color.index_tab_font_color'));
|
|
|
- TextInput({ placeholder: '例如: /music', text: this.filepath })
|
|
|
+ TextInput({ placeholder: '远程目录', text: this.filepath })
|
|
|
.layoutWeight(1)
|
|
|
.maxLines(1)
|
|
|
.onChange((value: string) => {
|
|
|
@@ -251,6 +281,7 @@ export struct WebDavAccountDialog {
|
|
|
});
|
|
|
}
|
|
|
.alignItems(VerticalAlign.Center);
|
|
|
+ this.buildHelperText('从共享根开始的路径,例如 /music 或 /音乐/歌单1')
|
|
|
|
|
|
// 用户名
|
|
|
Row({ space: 8 }) {
|
|
|
@@ -292,7 +323,9 @@ export struct WebDavAccountDialog {
|
|
|
.selectedColor(this.themeColor)
|
|
|
.onChange((isOn: boolean) => {
|
|
|
this.enableHttps = isOn;
|
|
|
- this.port = isOn ? 443 : 80;
|
|
|
+ if (!this.portCustomized && this.driveType === RemoteDriveType.WebDav) {
|
|
|
+ this.updatePortState(isOn ? 443 : 5005, false);
|
|
|
+ }
|
|
|
});
|
|
|
}
|
|
|
.width('100%');
|
|
|
@@ -374,25 +407,155 @@ export struct WebDavAccountDialog {
|
|
|
.backgroundColor(this.driveType === type ? this.themeColor : (this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')))
|
|
|
.fontColor(this.driveType === type ? Color.White : $r('app.color.index_tab_font_color'))
|
|
|
.onClick(() => {
|
|
|
- this.driveType = type;
|
|
|
- if (type === RemoteDriveType.Smb) {
|
|
|
- this.enableHttps = false;
|
|
|
- if (!this.port || this.port === 80 || this.port === 0) {
|
|
|
- this.port = 445;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (!this.port || this.port === 445) {
|
|
|
- this.port = this.enableHttps ? 443 : 80;
|
|
|
- }
|
|
|
- }
|
|
|
+ this.handleDriveTypeChange(type);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ private handleDriveTypeChange(type: RemoteDriveType) {
|
|
|
+ if (this.driveType === type) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.driveType = type;
|
|
|
+ if (type === RemoteDriveType.Smb) {
|
|
|
+ this.enableHttps = false;
|
|
|
+ }
|
|
|
+ this.applyTypeDefaults(type, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private applyTypeDefaults(type: RemoteDriveType, forcePort: boolean = false) {
|
|
|
+ if (!this.nameCustomized) {
|
|
|
+ this.accountName = this.getDefaultAccountName(type);
|
|
|
+ }
|
|
|
+ this.updatePortForType(type, forcePort);
|
|
|
+ }
|
|
|
+
|
|
|
+ private updatePortForType(type: RemoteDriveType, force: boolean = false) {
|
|
|
+ if (!force && this.portCustomized) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (type === RemoteDriveType.Smb) {
|
|
|
+ this.updatePortState(445, false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.updatePortState(this.enableHttps ? 443 : 5005, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ private getDefaultAccountName(type: RemoteDriveType): string {
|
|
|
+ return type === RemoteDriveType.Smb ? '新建SMB' : '新建WebDAV';
|
|
|
+ }
|
|
|
+
|
|
|
+ private getServerLabel(): string {
|
|
|
+ return '服务器';
|
|
|
+ }
|
|
|
+
|
|
|
+ private getServerHint(): string {
|
|
|
+ return this.driveType === RemoteDriveType.Smb
|
|
|
+ ? '支持 smb://user:pass@host/share 输入,自动拆分账户、共享、路径'
|
|
|
+ : '支持 https://user:pass@host:port/path 输入,自动填充协议、端口和目录';
|
|
|
+ }
|
|
|
+
|
|
|
private getPortPlaceholder(): string {
|
|
|
if (this.driveType === RemoteDriveType.Smb) {
|
|
|
return '默认: 445';
|
|
|
}
|
|
|
- return this.enableHttps ? '默认: 443' : '默认: 80';
|
|
|
+ return this.enableHttps ? '默认: 443' : '默认: 5005';
|
|
|
+ }
|
|
|
+
|
|
|
+ private updatePortState(value: number, customized: boolean) {
|
|
|
+ this.suppressPortChange = true;
|
|
|
+ this.port = value;
|
|
|
+ this.portCustomized = customized;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Builder
|
|
|
+ private buildHelperText(text: string) {
|
|
|
+ if (text && text.trim().length > 0) {
|
|
|
+ Text(text)
|
|
|
+ .fontSize(12)
|
|
|
+ .fontColor(this.isDarkMode ? '#8E8E93' : '#999999')
|
|
|
+ .opacity(0.9)
|
|
|
+ .alignSelf(ItemAlign.Start)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private tryParseConnectionString(input: string): boolean {
|
|
|
+ const parsed = this.parseConnectionString(input);
|
|
|
+ if (!parsed) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (parsed.protocol === 'http' || parsed.protocol === 'https') {
|
|
|
+ this.applyParsedWebDav(parsed);
|
|
|
+ ToastUtil.showToast('已解析 WebDAV 连接');
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (parsed.protocol === 'smb' || parsed.protocol === 'cifs') {
|
|
|
+ this.applyParsedSmb(parsed);
|
|
|
+ ToastUtil.showToast('已解析 SMB 连接');
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private applyParsedWebDav(parsed: ParsedConnectionParts): void {
|
|
|
+ this.handleDriveTypeChange(RemoteDriveType.WebDav);
|
|
|
+ this.enableHttps = parsed.protocol === 'https';
|
|
|
+ this.host = parsed.host;
|
|
|
+ if (parsed.port) {
|
|
|
+ this.updatePortState(parsed.port, true);
|
|
|
+ } else {
|
|
|
+ this.updatePortForType(RemoteDriveType.WebDav, true);
|
|
|
+ }
|
|
|
+ if (parsed.username) {
|
|
|
+ this.username = parsed.username;
|
|
|
+ }
|
|
|
+ if (parsed.password) {
|
|
|
+ this.password = parsed.password;
|
|
|
+ }
|
|
|
+ this.filepath = parsed.path?.length ? parsed.path : '/';
|
|
|
+ }
|
|
|
+
|
|
|
+ private applyParsedSmb(parsed: ParsedConnectionParts): void {
|
|
|
+ this.handleDriveTypeChange(RemoteDriveType.Smb);
|
|
|
+ this.host = parsed.host;
|
|
|
+ if (parsed.port) {
|
|
|
+ this.updatePortState(parsed.port, true);
|
|
|
+ } else {
|
|
|
+ this.updatePortForType(RemoteDriveType.Smb, true);
|
|
|
+ }
|
|
|
+ if (parsed.username) {
|
|
|
+ this.username = parsed.username;
|
|
|
+ }
|
|
|
+ if (parsed.password) {
|
|
|
+ this.password = parsed.password;
|
|
|
+ }
|
|
|
+ const segments = parsed.path?.replace(/^\/+/, '').split('/') ?? [];
|
|
|
+ this.shareName = segments.length > 0 && segments[0].length > 0 ? segments[0] : this.shareName;
|
|
|
+ const relative = segments.length > 1 ? segments.slice(1).join('/') : '';
|
|
|
+ this.filepath = relative ? `/${relative}` : '/';
|
|
|
+ }
|
|
|
+
|
|
|
+ private parseConnectionString(input: string): ParsedConnectionParts | null {
|
|
|
+ const pattern = /^([a-z][a-z0-9+\-.]*):\/\/(?:([^:@\/]+)(?::([^@\/]*))?@)?([^\/:]+)(?::(\d+))?(\/.*)?$/i;
|
|
|
+ const match = input.match(pattern);
|
|
|
+ if (!match) {
|
|
|
+ Logger.warn('heanup RemoteDriveAccountDialog', `连接字符串解析失败: ${input}`);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ const protocol = match[1].toLowerCase();
|
|
|
+ const username = match[2] ? this.safeDecode(match[2]) : undefined;
|
|
|
+ const password = match[3] ? this.safeDecode(match[3]) : undefined;
|
|
|
+ const host = match[4];
|
|
|
+ const port = match[5] ? Number(match[5]) : undefined;
|
|
|
+ const path = match[6] && match[6].length > 0 ? match[6] : undefined;
|
|
|
+ return { protocol, username, password, host, port, path };
|
|
|
+ }
|
|
|
+
|
|
|
+ private safeDecode(value: string): string {
|
|
|
+ try {
|
|
|
+ return decodeURIComponent(value);
|
|
|
+ } catch (_error) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -414,10 +577,10 @@ export struct WebDavAccountDialog {
|
|
|
ImagePickerUtil.deleteImage(this.coverPath)
|
|
|
}
|
|
|
this.coverPath = selectedPath
|
|
|
- Logger.info('heanup WebDavAccountDialog', `选择封面成功: ${selectedPath}`)
|
|
|
+ Logger.info('heanup RemoteDriveAccountDialog', `选择封面成功: ${selectedPath}`)
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- Logger.error('heanup WebDavAccountDialog', `选择封面失败: ${(error as Error).message}`)
|
|
|
+ Logger.error('heanup RemoteDriveAccountDialog', `选择封面失败: ${(error as Error).message}`)
|
|
|
ToastUtil.showToast('选择封面失败')
|
|
|
}
|
|
|
}
|
|
|
@@ -436,7 +599,7 @@ export struct WebDavAccountDialog {
|
|
|
ImagePickerUtil.deleteImage(this.coverPath)
|
|
|
}
|
|
|
this.coverPath = ''
|
|
|
- Logger.info('heanup WebDavAccountDialog', '移除封面成功')
|
|
|
+ Logger.info('heanup RemoteDriveAccountDialog', '移除封面成功')
|
|
|
}
|
|
|
}
|
|
|
}
|