fix(auth): 访客工作台 - 欢迎页关闭后展示创作界面 (#697)
Co-authored-by: a0 <a0@Mac-mini.local>
This commit is contained in:
73
web/src/pages/create/creation-login-dialog.tsx
Normal file
73
web/src/pages/create/creation-login-dialog.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useState, type FormEvent } from "react";
|
||||
import { App, Button, Input } from "antd";
|
||||
import { ArrowRight, LockKeyhole, UserRound } from "lucide-react";
|
||||
import { Link } from "react-router";
|
||||
|
||||
import { AppModal } from "@/components/ui/product/app-modal";
|
||||
import { getAuthSession, login } from "@/services/api/auth";
|
||||
|
||||
export function CreationLoginDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
|
||||
const { message } = App.useApp();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setUsername("");
|
||||
setPassword("");
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const submit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (submitting) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await login({ username: username.trim(), password });
|
||||
const { applyUserSession } = await import("@/lib/user-session");
|
||||
await applyUserSession(await getAuthSession());
|
||||
message.success("登录成功,请再次点击生成");
|
||||
onClose();
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : "登录失败");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppModal open={open} onCancel={onClose} title="登录后开始创作" footer={null} centered width="min(440px, calc(100vw - 32px))">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-foreground/60">你的创作内容会保留。登录成功后,请再次点击生成。</p>
|
||||
<form className="space-y-3" onSubmit={(event) => void submit(event)}>
|
||||
<Input
|
||||
size="large"
|
||||
aria-label="用户名或邮箱"
|
||||
prefix={<UserRound className="size-4 text-foreground/40" />}
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
placeholder="用户名或邮箱"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
<Input.Password
|
||||
size="large"
|
||||
aria-label="密码"
|
||||
prefix={<LockKeyhole className="size-4 text-foreground/40" />}
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder="密码"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<Button type="primary" htmlType="submit" size="large" block loading={submitting} icon={<ArrowRight className="size-4" />} iconPlacement="end">
|
||||
登录并继续
|
||||
</Button>
|
||||
</form>
|
||||
<p className="text-center text-xs text-foreground/55">还没有账号? <Link className="text-primary hover:underline" to="/register?next=%2F" onClick={onClose}>注册</Link></p>
|
||||
</div>
|
||||
</AppModal>
|
||||
);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import { CreationComposer, CreationEmptySuggest, CreationFeaturedWorks, Creation
|
||||
import { CreationAgentEntry } from "./creation-agent-entry";
|
||||
import { createCreationSubmitGate } from "./creation-submit-gate";
|
||||
import { creationVideoConfig } from "./creation-generation-config";
|
||||
import { CreationLoginDialog } from "./creation-login-dialog";
|
||||
|
||||
const AssetLibraryPickerModal = lazy(() => import("@/components/assets/asset-library-picker-modal").then((module) => ({ default: module.AssetLibraryPickerModal })));
|
||||
const loadCreationRuntime = () => import("./creation-runtime");
|
||||
@@ -97,6 +98,9 @@ export default function CreatePage() {
|
||||
const [activeId, setActiveId] = useState("");
|
||||
const activeIdRef = useRef("");
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const userId = useUserStore((state) => state.user?.id || null);
|
||||
const userSessionHydrated = useUserStore((state) => state.hydrated);
|
||||
const [conversationScope, setConversationScope] = useState<string | null>(null);
|
||||
const [mode, setMode] = useState<CreationMode>(() => initialComposerPreferences.mode || defaultCreationMode);
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [attachments, setAttachments] = useState<CreationAttachment[]>([]);
|
||||
@@ -115,6 +119,7 @@ export default function CreatePage() {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [referenceReplacementBusy, setReferenceReplacementBusy] = useState(false);
|
||||
const [historyOpen, setHistoryOpen] = useState(false);
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
|
||||
const [libraryOpen, setLibraryOpen] = useState(false);
|
||||
const externalAssetSources = useExternalAssetSources(libraryOpen);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
@@ -256,20 +261,25 @@ export default function CreatePage() {
|
||||
}, [attachments, maxReferences, mentionReferences, mode, videoReferenceLimits]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userSessionHydrated) return;
|
||||
let cancelled = false;
|
||||
const scope = getActiveUserScope();
|
||||
setHydrated(false);
|
||||
setConversationScope(null);
|
||||
void loadCreationConversations<CreationConversation>().then((stored) => {
|
||||
if (cancelled) return;
|
||||
if (cancelled || getActiveUserScope() !== scope || (useUserStore.getState().user?.id || null) !== userId) return;
|
||||
const next = stored?.length ? stored : [newConversation()];
|
||||
conversationsRef.current = next;
|
||||
setConversations(next);
|
||||
setActiveId(next[0].id);
|
||||
setConversationScope(scope);
|
||||
setHydrated(true);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
// 页面卸载只停止当前页面的状态更新,后台任务由任务中心继续执行,返回页面后再恢复状态。
|
||||
};
|
||||
}, []);
|
||||
}, [userId, userSessionHydrated]);
|
||||
|
||||
useEffect(() => () => abortRef.current?.abort(), []);
|
||||
|
||||
@@ -279,8 +289,8 @@ export default function CreatePage() {
|
||||
|
||||
useEffect(() => {
|
||||
conversationsRef.current = conversations;
|
||||
if (hydrated) void saveCreationConversations(conversations);
|
||||
}, [conversations, hydrated]);
|
||||
if (hydrated && userSessionHydrated && conversationScope === getActiveUserScope()) void saveCreationConversations(conversations);
|
||||
}, [conversations, conversationScope, hydrated, userSessionHydrated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !recoveryTaskKey || !pendingTaskIds.length) return;
|
||||
@@ -570,6 +580,12 @@ export default function CreatePage() {
|
||||
releaseSubmitGate();
|
||||
return;
|
||||
}
|
||||
if (!useUserStore.getState().user) {
|
||||
setLoginDialogOpen(true);
|
||||
releaseRetryLock();
|
||||
releaseSubmitGate();
|
||||
return;
|
||||
}
|
||||
if (!selectedModel) {
|
||||
toast.warning(`请先在设置中配置${modeLabels[mode]}模型`);
|
||||
releaseRetryLock();
|
||||
@@ -1066,6 +1082,7 @@ export default function CreatePage() {
|
||||
</div>}
|
||||
</div>
|
||||
<CreationHistoryDrawer open={historyOpen} conversations={historyConversations} activeId={activeConversation.id} onNew={startNewConversation} onClose={() => setHistoryOpen(false)} onSelect={selectConversation} onDelete={confirmDeleteConversation} onRename={renameConversationTitle} />
|
||||
<CreationLoginDialog open={loginDialogOpen} onClose={() => setLoginDialogOpen(false)} />
|
||||
{libraryOpen ? <Suspense fallback={null}><AssetLibraryPickerModal
|
||||
remoteLibrary
|
||||
open={libraryOpen}
|
||||
|
||||
Reference in New Issue
Block a user