feat(auth): 独立登录会话开关(设置-外观)—— 同浏览器多账号互不覆盖

复用 iframe 嵌入的按标签页隔离机制:新增 ?isolate=1 标志,authStorage()
从「仅嵌入」放宽为 isEmbedActive() || isIsolatedSession(),独立态走
sessionStorage 故本标签页登录态不被其它标签页覆盖。设置→外观→「登录会话」
加 Switch,开启时先 migrateAuthToSession() 把 localStorage 受保护键快照搬进
sessionStorage 再翻标志,无感不登出;AuthSyncWatcher 跨标签监听对隔离态实时
忽略,运行时开关即刻生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-19 17:51:08 +08:00
1 parent a9f01bec79
commit a85d2480b4
5 files changed
+146 -15

No files matched your search

@@ -1,7 +1,7 @@
"use client";
import { DropletIcon, LayoutGridIcon, LayoutIcon, ListIcon, MoonIcon, PanelLeftIcon, SunIcon } from "lucide-react";
import { useMemo, type ComponentType, type SVGProps } from "react";
import { useMemo, useState, type ComponentType, type SVGProps } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
@@ -20,6 +20,8 @@ import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { getStoredAuth } from "@/core/auth";
import { migrateAuthToSession } from "@/core/auth/scoped-storage";
import { isIsolatedSession, setIsolatedSession } from "@/core/embed/embed-session";
import { enUS, isLocale, zhCN, type Locale } from "@/core/i18n";
import { useI18n } from "@/core/i18n/hooks";
import { useLocalSettings } from "@/core/settings";
@@ -64,6 +66,29 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
const cleanupWords =
citationDisplay?.cleanup_words ?? settings.display.citationCleanupWords ?? [];
// 独立登录会话(本标签页):开关切换鉴权存储 localStorage ↔ sessionStorage(见 scoped-storage)。
const [isolatedSession, setIsolatedSessionState] = useState(() => isIsolatedSession());
const handleToggleIsolatedSession = (checked: boolean) => {
if (checked) {
// 先把当前账号从 localStorage 快照搬进 sessionStorage,再翻标志,避免切换存储后被登出。
migrateAuthToSession();
setIsolatedSession(true);
setIsolatedSessionState(true);
toast.success("已开启独立登录会话", {
description:
"本标签页的登录已与其它标签页隔离:在别的窗口登录或切换其它账号都不会影响这里,你也可以在此单独登录另一个账号。该独立会话只存在于当前标签页,关闭此标签后即结束(需重新登录)。",
duration: 9000,
});
} else {
setIsolatedSession(false);
setIsolatedSessionState(false);
toast.info("已关闭独立登录会话", {
description: "本标签页恢复与其它标签页共享同一登录。如页面显示未及时同步,请刷新本页。",
duration: 6000,
});
}
};
const handleSetGlobalDefault = async (
value: "light" | "dark" | "lightblue",
) => {
@@ -395,6 +420,28 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
<Separator />
<SettingsSection
title="登录会话"
description="控制本标签页的登录身份是否与浏览器其它标签页隔离。"
>
<div className="flex items-start justify-between gap-4 rounded-lg border p-4">
<div>
<div className="text-sm font-medium">独立登录会话(本标签页)</div>
<p className="text-muted-foreground mt-1 max-w-md text-xs leading-relaxed">
开启后,本标签页的登录状态仅保存在当前标签页:在别的窗口登录或切换其它账号都不会影响这里,
你也可以在此单独登录另一个账号。关闭浏览器标签后该独立会话即结束(需重新登录)。
</p>
</div>
<Switch
checked={isolatedSession}
onCheckedChange={handleToggleIsolatedSession}
aria-label="独立登录会话"
/>
</div>
</SettingsSection>
<Separator />
<SettingsSection
title="对话体验"
description="问答过程中的辅助显示。"