chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View 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)
}
}