chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
213
client/harmony/entry/src/main/ets/pages/LoginPage.ets
Normal file
213
client/harmony/entry/src/main/ets/pages/LoginPage.ets
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* AgentMail 鸿蒙客户端 — 登录页
|
||||
* 两种模式:账号密码(Cookie) / 用户密钥(Bearer)
|
||||
* 服务器地址可配置(跨设备/模拟器场景)
|
||||
*/
|
||||
import { ApiClient, ApiError } from '../api/ApiClient';
|
||||
import { AuthApi } from '../api/AuthApi';
|
||||
import { AccountManager } from '../api/AccountManager';
|
||||
import { SseService } from '../api/SseService';
|
||||
import { Me } from '../model/Models';
|
||||
import { DEFAULT_API_BASE, EMULATOR_HOST_BASE } from '../common/Config';
|
||||
import { promptAction } from '@kit.ArkUI';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct LoginPage {
|
||||
@State serverAddr: string = DEFAULT_API_BASE;
|
||||
@State username: string = '';
|
||||
@State password: string = '';
|
||||
@State userKey: string = '';
|
||||
@State mode: number = 1; // 0 = 账号密码, 1 = 用户密钥
|
||||
@State loading: boolean = false;
|
||||
@State loggedIn: boolean = false;
|
||||
@State me: Me = new Me();
|
||||
|
||||
private client: ApiClient | null = null;
|
||||
private authApi: AuthApi | null = null;
|
||||
|
||||
aboutToAppear(): void {
|
||||
const ctx = this.getUIContext().getHostContext();
|
||||
if (ctx !== undefined) {
|
||||
const client: ApiClient = ApiClient.getInstance(ctx);
|
||||
this.client = client;
|
||||
this.authApi = new AuthApi(client);
|
||||
// 检查多账号管理器:已有账号则自动恢复
|
||||
const acctMgr: AccountManager = AccountManager.getInstance(ctx);
|
||||
acctMgr.load().then(() => {
|
||||
const active = acctMgr.getActiveAccount();
|
||||
if (active !== null) {
|
||||
// 恢复活跃账号的 token
|
||||
client.setBase(active.server);
|
||||
client.setToken(active.token);
|
||||
this.serverAddr = active.server;
|
||||
// 为所有账号建立 SSE 连接
|
||||
SseService.getInstance().connectAll(acctMgr);
|
||||
this.getUIContext().getRouter().pushUrl({ url: 'pages/MainPage' });
|
||||
return;
|
||||
}
|
||||
// 无账号,走正常登录流程
|
||||
client.init().then(() => {
|
||||
const base: string = client.getBase();
|
||||
this.serverAddr = base;
|
||||
const token: string = client.getToken();
|
||||
if (token.length > 0) {
|
||||
this.tryRestore(token);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async tryRestore(token: string): Promise<void> {
|
||||
this.loading = true;
|
||||
try {
|
||||
const c: ApiClient | null = this.client;
|
||||
if (c === null) {
|
||||
return;
|
||||
}
|
||||
const user = await new AuthApi(c).loginWithKey(token);
|
||||
this.me = user;
|
||||
this.loggedIn = true;
|
||||
} catch (e) {
|
||||
const c2: ApiClient | null = this.client;
|
||||
if (c2 !== null) {
|
||||
c2.clearAuth();
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async doLogin(): Promise<void> {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
const c: ApiClient | null = this.client;
|
||||
const a: AuthApi | null = this.authApi;
|
||||
if (c === null || a === null) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
// 保存服务器地址(覆盖默认)
|
||||
await c.setBase(this.stripTrailingSlash(this.serverAddr));
|
||||
await c.persistBase(this.stripTrailingSlash(this.serverAddr));
|
||||
hilog.info(0x0001, 'LoginPage', 'base saved, calling login');
|
||||
|
||||
let user: Me;
|
||||
if (this.mode === 0) {
|
||||
// 账号密码:调 login 拿 user,token 交给 cookie 管理
|
||||
user = await a.login(this.username, this.password);
|
||||
} else {
|
||||
// 用户密钥
|
||||
user = await a.loginWithKey(this.userKey.trim());
|
||||
}
|
||||
hilog.info(0x0001, 'LoginPage', 'login ok: %{public}s', user.username);
|
||||
this.me = user;
|
||||
this.loggedIn = true;
|
||||
// 存入多账号管理器
|
||||
const ctx: Context | undefined = this.getUIContext().getHostContext();
|
||||
if (ctx !== undefined) {
|
||||
const acctMgr: AccountManager = AccountManager.getInstance(ctx);
|
||||
await acctMgr.load();
|
||||
const token: string = this.mode === 1 ? this.userKey.trim() : c.getToken();
|
||||
const server: string = c.getBase();
|
||||
const acct = await acctMgr.addAccount(server, user.username, token, user.display_name.length > 0 ? user.display_name : user.username);
|
||||
// 为该账号建立 SSE 连接
|
||||
SseService.getInstance().connectForAccount(acct.id, acct.server, acct.token);
|
||||
}
|
||||
// 跳转主界面(含 Tab 导航)
|
||||
await this.getUIContext().getRouter().pushUrl({ url: 'pages/MainPage' });
|
||||
} catch (e) {
|
||||
const ae = e as ApiError;
|
||||
const msg: string = ae.code === 0 ? ae.message : (ae.message.length > 0 ? ae.message : '登录失败');
|
||||
hilog.error(0x0001, 'LoginPage', 'login failed: code=%{public}d msg=%{public}s', ae.code, msg);
|
||||
promptAction.showToast({ message: msg });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
stripTrailingSlash(s: string): string {
|
||||
let v: string = s.trim();
|
||||
while (v.length > 0 && v.endsWith('/')) {
|
||||
v = v.substring(0, v.length - 1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 标题区
|
||||
Column() {
|
||||
Text('AgentMail')
|
||||
.fontSize(28)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor('#1A73E8')
|
||||
Text('邮件驱动 · 多智能体协作平台')
|
||||
.fontSize(14)
|
||||
.fontColor('#666666')
|
||||
.margin({ top: 6 })
|
||||
}
|
||||
.margin({ top: 100, bottom: 48 })
|
||||
|
||||
// 服务器地址
|
||||
TextInput({ placeholder: '服务器地址', text: this.serverAddr })
|
||||
.width('85%')
|
||||
.height(48)
|
||||
.margin({ bottom: 12 })
|
||||
.onChange((v: string) => { this.serverAddr = v; })
|
||||
|
||||
// 切换登录方式
|
||||
Row() {
|
||||
Text('账号密码').fontSize(14).fontColor(this.mode === 0 ? '#1A73E8' : '#999999')
|
||||
.onClick(() => { this.mode = 0; })
|
||||
Text(' | ').fontSize(14).fontColor('#cccccc')
|
||||
Text('用户密钥').fontSize(14).fontColor(this.mode === 1 ? '#1A73E8' : '#999999')
|
||||
.onClick(() => { this.mode = 1; })
|
||||
}
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
if (this.mode === 0) {
|
||||
TextInput({ placeholder: '用户名', text: this.username })
|
||||
.width('85%').height(48).margin({ bottom: 12 })
|
||||
.onChange((v: string) => { this.username = v; })
|
||||
TextInput({ placeholder: '密码', text: this.password })
|
||||
.width('85%').height(48).margin({ bottom: 24 })
|
||||
.type(InputType.Password)
|
||||
.onChange((v: string) => { this.password = v; })
|
||||
} else {
|
||||
TextInput({ placeholder: '粘贴用户密钥 (Bearer token)', text: this.userKey })
|
||||
.width('85%').height(48).margin({ bottom: 24 })
|
||||
.onChange((v: string) => { this.userKey = v; })
|
||||
}
|
||||
|
||||
Button(this.loading ? '登录中…' : '登 录')
|
||||
.width('85%').height(48)
|
||||
.backgroundColor('#1A73E8')
|
||||
.fontSize(16)
|
||||
.enabled(!this.loading)
|
||||
.onClick(() => {
|
||||
hilog.info(0x0001, 'LoginPage', 'onClick fired, loading=%{public}s', this.loading.toString());
|
||||
this.doLogin();
|
||||
})
|
||||
|
||||
// 模拟器备选地址提示
|
||||
Text('模拟器 NAT 不通时用 ' + EMULATOR_HOST_BASE)
|
||||
.fontSize(11).fontColor('#aaaaaa').margin({ top: 20 })
|
||||
|
||||
if (this.loggedIn) {
|
||||
// 登录成功 → 进入主界面(占位,后续 M2 替换)
|
||||
Blank()
|
||||
Text('登录成功:' + this.me.username)
|
||||
.fontSize(16).fontColor('#1A73E8').margin({ bottom: 60 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('#F5F7FA')
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user