diff --git a/sundynix-desktop/frontend/src/lib/api.ts b/sundynix-desktop/frontend/src/lib/api.ts
index d8efab9..11d848a 100644
--- a/sundynix-desktop/frontend/src/lib/api.ts
+++ b/sundynix-desktop/frontend/src/lib/api.ts
@@ -1,9 +1,42 @@
// Gateway HTTP/SSE 客户端:提交 DSL 任务、订阅 Token 流、登记偏好记忆。
import type { TaskDsl } from "./dsl";
-// 开发期直连 Gateway;Wails 打包后可改为本地后端地址或经 Go 绑定。
-export const GATEWAY: string =
- (import.meta.env.VITE_GATEWAY as string | undefined) ?? "http://localhost:8080";
+// 服务器地址:**运行时可配**(登录页可填),优先级 localStorage > 构建期 VITE_GATEWAY > 本地默认。
+// 做成运行时而非只在构建期写死,是因为同一个 App 要能在「本地全栈」和「线上服务器」之间随时切,
+// 每换一次环境就重编一次客户端不现实。改完需重载窗口(模块级常量,40+ 处调用点靠它读同一份值)。
+const GATEWAY_KEY = "sdx_gateway";
+
+function initialGateway(): string {
+ try {
+ const saved = localStorage.getItem(GATEWAY_KEY);
+ if (saved) return saved.replace(/\/+$/, ""); // 去掉尾斜杠,否则拼出 //api/v1 这种路径
+ } catch {
+ /* 隐私模式忽略 */
+ }
+ return (import.meta.env.VITE_GATEWAY as string | undefined) ?? "http://localhost:8080";
+}
+
+export const GATEWAY: string = initialGateway();
+
+// setGatewayURL 保存服务器地址(空串=清除,回落构建期默认)。调用方保存后应重载窗口使其生效。
+export function setGatewayURL(url: string): void {
+ try {
+ const v = url.trim().replace(/\/+$/, "");
+ if (v) localStorage.setItem(GATEWAY_KEY, v);
+ else localStorage.removeItem(GATEWAY_KEY);
+ } catch {
+ /* ignore */
+ }
+}
+
+// customGatewayURL 返回用户自定义的地址(没设过则空串),供设置界面回显。
+export function customGatewayURL(): string {
+ try {
+ return localStorage.getItem(GATEWAY_KEY) ?? "";
+ } catch {
+ return "";
+ }
+}
export interface Identity {
userId: string;
diff --git a/sundynix-desktop/frontend/src/views/Login.tsx b/sundynix-desktop/frontend/src/views/Login.tsx
index bb77b5d..b123db2 100644
--- a/sundynix-desktop/frontend/src/views/Login.tsx
+++ b/sundynix-desktop/frontend/src/views/Login.tsx
@@ -1,6 +1,6 @@
import { useState } from "react";
import { LogIn, UserPlus, Loader2 } from "lucide-react";
-import { authLogin, authRegister, type AuthUser } from "../lib/api";
+import { authLogin, authRegister, customGatewayURL, setGatewayURL, GATEWAY, type AuthUser } from "../lib/api";
import { Button, Input, Field } from "../ui";
// Login:未登录时的全屏鉴权门。登录/注册成功后回调 onAuthed 把用户交给 App。
@@ -66,7 +66,49 @@ export function Login({ onAuthed }: { onAuthed: (u: AuthUser) => void }) {
{isRegister ? "去登录" : "去注册"}
+
+