feat(desktop): 顶栏租户切换器(多租户)—— 切换活跃租户即换工作区+计费
用户属多个租户时,顶栏显示租户下拉(Building2 图标):切换即调 POST /me/tenant 换活跃租户,随后刷新余额芯片(显示新计费租户的可花余额)。仅 >1 租户时出现。 api 加 myTenants/switchTenant。 live 验证(preview):demoB(属公司A+公司B) 切换器显两租户;从公司A(共享,余额15.84) 切到公司B(个人,余额6.38)→ 后端 active 持久化、芯片随之更新为 6.4(橙,偏低)。tsc+48 测试全过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ import { Placeholder } from "./views/Placeholder";
|
||||
import { CommandPalette, type Command } from "./components/CommandPalette";
|
||||
import { UpdateBanner } from "./components/UpdateBanner";
|
||||
import { Login } from "./views/Login";
|
||||
import { submitTask, streamTokens, streamExec, taskStatus, listRuns, authMe, logout, tenantCurrent, type Identity, type AuthUser, type TenantCtx } from "./lib/api";
|
||||
import { submitTask, streamTokens, streamExec, taskStatus, listRuns, authMe, logout, tenantCurrent, myTenants, switchTenant, type Identity, type AuthUser, type TenantCtx, type MyTenant } from "./lib/api";
|
||||
import type { TaskDsl } from "./lib/dsl";
|
||||
import { emptyRun, type RunState } from "./lib/run";
|
||||
import { ToastProvider } from "./ui";
|
||||
@@ -43,6 +43,7 @@ export default function App() {
|
||||
const [view, setView] = useState<ViewKey>("home");
|
||||
const [user, setUser] = useState<AuthUser | null>(null);
|
||||
const [tenant, setTenant] = useState<TenantCtx | null>(null);
|
||||
const [tenants, setTenants] = useState<MyTenant[]>([]);
|
||||
const [authLoading, setAuthLoading] = useState(true);
|
||||
const identity = useMemo<Identity>(() => ({ userId: user?.id ?? "", sessionId: getSessionId() }), [user]);
|
||||
const [run, setRun] = useState<RunState>(emptyRun);
|
||||
@@ -92,10 +93,25 @@ export default function App() {
|
||||
tenantCurrent()
|
||||
.then(setTenant)
|
||||
.catch(() => {});
|
||||
myTenants()
|
||||
.then((r) => setTenants(r.tenants))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
const onSwitchTenant = useCallback(
|
||||
async (id: string) => {
|
||||
try {
|
||||
await switchTenant(id);
|
||||
refreshTenant();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
[refreshTenant],
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setTenant(null);
|
||||
setTenants([]);
|
||||
return;
|
||||
}
|
||||
refreshTenant();
|
||||
@@ -239,7 +255,7 @@ export default function App() {
|
||||
style={{ background: "radial-gradient(60% 100% at 50% 0%, rgba(124,92,246,0.10), transparent 70%)" }}
|
||||
/>
|
||||
<UpdateBanner />
|
||||
<TopBar user={user} tenant={tenant} onLogout={onLogout} onCommand={() => setCmdOpen(true)} onOpenUsage={() => setView("usage")} />
|
||||
<TopBar user={user} tenant={tenant} tenants={tenants} onSwitchTenant={onSwitchTenant} onLogout={onLogout} onCommand={() => setCmdOpen(true)} onOpenUsage={() => setView("usage")} />
|
||||
<ApprovalBar run={run} />
|
||||
<div className="relative flex min-h-0 flex-1">
|
||||
<LeftNav active={view} onSelect={setView} />
|
||||
|
||||
Reference in New Issue
Block a user