feat(desktop): 服务器地址运行时可配(登录页可填,不必重编客户端)
此前 GATEWAY 只在构建期由 VITE_GATEWAY 写死,客户端连线上后端就得重新打包, 换一次环境编一次不现实。 改:优先级 localStorage > 构建期 VITE_GATEWAY > localhost:8080; 登录页加折叠的「服务器地址」输入(必须在登录前可改——地址不对连都连不上, 更谈不上登录);保存后自动重载窗口生效(模块级常量在加载时读定)。 顺带:地址统一去尾斜杠,否则拼出 //api/v1 这种路径。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 ? "去登录" : "去注册"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ServerField />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ServerField 服务器地址设置:默认折叠,展开后可指向线上后端或本地全栈。
|
||||
// 放在登录页是因为它必须在登录之前可改——地址不对就连不上、更谈不上登录。
|
||||
function ServerField() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [url, setUrl] = useState(customGatewayURL());
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
const save = () => {
|
||||
setGatewayURL(url);
|
||||
setSaved(true);
|
||||
// 模块级常量在加载时读定,改完必须重载窗口才生效——直接替用户重载,省得他困惑为何没变。
|
||||
setTimeout(() => window.location.reload(), 400);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 border-t border-line pt-3">
|
||||
<button className="text-[11px] text-slate-500 transition hover:text-slate-300" onClick={() => setOpen((v) => !v)}>
|
||||
{open ? "▾" : "▸"} 服务器地址
|
||||
<span className="ml-1 text-slate-600">{GATEWAY}</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="mt-2 space-y-2">
|
||||
<Input
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder="https://agent.sundynix.cn"
|
||||
onKeyDown={(e) => e.key === "Enter" && save()}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" onClick={save} disabled={saved}>
|
||||
{saved ? "已保存,重载中…" : "保存并重载"}
|
||||
</Button>
|
||||
<span className="text-[11px] text-slate-600">留空 = 用默认</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user