- Dark-blue theme background assets and styling integration - Appearance settings with layoutMode (full/compact) preference - Settings dialog and auth context updates - AI writing form and panel refinements - Open Canvas context updates - Roundtable planning styling and dashboard enhancements - Strategy components and header updates - Light app management and routing improvements - Global style enhancements for dark theme - Various page layout and workspace routing optimizations
182 lines
6.3 KiB
TypeScript
182 lines
6.3 KiB
TypeScript
'use client';
|
||
|
||
import { useParams, useRouter, useSearchParams } from 'next/navigation';
|
||
import { useEffect, useMemo, useState } from 'react';
|
||
|
||
import { useTheme } from '@/contexts/ThemeContext';
|
||
import { loginByToken, loginByUsername } from '@/core/auth/api';
|
||
import { setStoredAuth, setStoredUserName } from '@/core/auth';
|
||
import {
|
||
persistExternalLoginParams,
|
||
readExternalLoginParams,
|
||
resolveThemeFromQuery,
|
||
} from '@/core/auth/external-login';
|
||
import { resolveUsernameByOnlineTicket } from '@/core/auth/wujie-ticket-login';
|
||
import { isWujieEnv, useWujieProps } from '@/core/embed/wujie';
|
||
import {
|
||
GENERAL_QA_ROUTE,
|
||
loginByYUserId,
|
||
persistToolsToken,
|
||
} from '@/core/auth/yuser-login';
|
||
import {
|
||
isSidebarEnabledFromSearch,
|
||
persistSidebarEnabled,
|
||
} from '@/core/page-layout/sidebar-enabled';
|
||
|
||
const DEFAULT_USERNAME = 'guest';
|
||
const IFRAME_LOGIN_SOURCE = 'iframe';
|
||
const DEFAULT_REDIRECT_PATH = '/page/workspace/chats/new';
|
||
const IFRAME_REDIRECT_PATH = '/page/workspace/chats_iframe/new';
|
||
|
||
type LoginStatus = 'loading' | 'success' | 'error';
|
||
|
||
export default function LoginPage() {
|
||
const { username } = useParams<{ username?: string }>();
|
||
const router = useRouter();
|
||
const searchParams = useSearchParams();
|
||
const { setTheme } = useTheme();
|
||
const sidebarEnabled = useMemo(
|
||
() => isSidebarEnabledFromSearch(searchParams.toString()),
|
||
[searchParams],
|
||
);
|
||
const redirectPath = useMemo(
|
||
() =>
|
||
searchParams.get('from') === IFRAME_LOGIN_SOURCE
|
||
? IFRAME_REDIRECT_PATH
|
||
: DEFAULT_REDIRECT_PATH,
|
||
[searchParams],
|
||
);
|
||
const resolvedUsername = useMemo(() => {
|
||
const raw = username?.trim();
|
||
return raw && raw.length > 0 ? raw : DEFAULT_USERNAME;
|
||
}, [username]);
|
||
// 外部跳转参数:theme 直接调 setTheme;ticket/userId/env 持久化到 localStorage
|
||
const yUserId = searchParams.get('yUserId')?.trim() ?? '';
|
||
const toolToken = searchParams.get('token')?.trim() ?? '';
|
||
// 用户名直登口令门:开启后端开关时,口令从 ?password= 透传
|
||
const gatePassword = searchParams.get('password') ?? '';
|
||
// token 换登录:?authToken= 传上游 token(与 tools_token 的 ?token= 区分开)
|
||
const authToken = searchParams.get('authToken')?.trim() ?? '';
|
||
const externalParams = useMemo(
|
||
() => ({
|
||
theme: searchParams.get('theme') ?? '',
|
||
ticket: searchParams.get('ticket') ?? '',
|
||
userId: searchParams.get('userId') ?? '',
|
||
env: searchParams.get('env') ?? '',
|
||
}),
|
||
[searchParams],
|
||
);
|
||
const [status, setStatus] = useState<LoginStatus>('loading');
|
||
const [errorMessage, setErrorMessage] = useState<string>('');
|
||
|
||
// 无界(wujie)父级通过 $wujie.props 注入的参数(ticket / userId / theme / env)。
|
||
// hook 内部已把 ticket/userId/env 持久化到 localStorage(复用 external-login 存储)。
|
||
const wujieProps = useWujieProps();
|
||
|
||
/** onlineTicket:无界 props 优先,其次 URL query,最后读 localStorage */
|
||
const onlineTicket = useMemo(() => {
|
||
const fromWujie = wujieProps.ticket?.trim() ?? "";
|
||
const fromQuery = externalParams.ticket.trim();
|
||
if (fromWujie || fromQuery) {
|
||
return fromWujie || fromQuery;
|
||
}
|
||
return readExternalLoginParams().ticket.trim();
|
||
}, [wujieProps.ticket, externalParams.ticket]);
|
||
|
||
// 调试:把无界注入的 props 打印出来(仅在无界子应用环境下,避免独立访问时刷日志)。
|
||
useEffect(() => {
|
||
if (!isWujieEnv()) return;
|
||
console.log('[wujie] sub-app props:', wujieProps);
|
||
}, [wujieProps]);
|
||
|
||
// 主题切换:无界 props.theme 优先,其次 URL query theme。两者取值约定一致
|
||
// (dark-blue → 深色;red → 浅色;blue 暂无该主题,先用浅色代替),统一走 resolveThemeFromQuery。
|
||
useEffect(() => {
|
||
const themeValue = wujieProps.theme || externalParams.theme;
|
||
if (!themeValue) return;
|
||
setTheme(resolveThemeFromQuery(themeValue));
|
||
}, [wujieProps.theme, externalParams.theme, setTheme]);
|
||
|
||
// ticket / userId / env 持久化(与 login 请求并行做,互不阻塞)
|
||
useEffect(() => {
|
||
persistExternalLoginParams({
|
||
ticket: onlineTicket || externalParams.ticket,
|
||
userId: externalParams.userId,
|
||
env: externalParams.env,
|
||
});
|
||
}, [onlineTicket, externalParams.ticket, externalParams.userId, externalParams.env]);
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
|
||
async function runLogin() {
|
||
setStatus('loading');
|
||
setErrorMessage('');
|
||
try {
|
||
let auth;
|
||
let loginUserName = '';
|
||
if (authToken) {
|
||
// token 换登录:后端用 authToken 调上游接口换出用户名再登录
|
||
auth = await loginByToken(authToken);
|
||
} else {
|
||
let deerflowUsername = resolvedUsername;
|
||
if (onlineTicket) {
|
||
deerflowUsername = await resolveUsernameByOnlineTicket(onlineTicket);
|
||
} else if (yUserId) {
|
||
await loginByYUserId(yUserId, toolToken || null);
|
||
} else if (toolToken) {
|
||
persistToolsToken(toolToken);
|
||
}
|
||
|
||
auth = await loginByUsername(deerflowUsername, gatePassword);
|
||
loginUserName = deerflowUsername;
|
||
}
|
||
if (cancelled) {
|
||
return;
|
||
}
|
||
setStoredAuth(auth);
|
||
// 标准登录把用户名写入 userInfo,供顶栏用户区显示(已有则不覆盖)
|
||
setStoredUserName(loginUserName);
|
||
persistSidebarEnabled(sidebarEnabled);
|
||
setStatus('success');
|
||
router.replace(yUserId ? GENERAL_QA_ROUTE : redirectPath);
|
||
} catch (error) {
|
||
if (cancelled) {
|
||
return;
|
||
}
|
||
setStatus('error');
|
||
setErrorMessage(
|
||
error instanceof Error ? error.message : 'Login request failed.',
|
||
);
|
||
}
|
||
}
|
||
|
||
void runLogin();
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [
|
||
resolvedUsername,
|
||
router,
|
||
sidebarEnabled,
|
||
yUserId,
|
||
toolToken,
|
||
onlineTicket,
|
||
authToken,
|
||
gatePassword,
|
||
]);
|
||
|
||
return (
|
||
<div className="flex min-h-screen items-center justify-center bg-background px-6">
|
||
<div className="w-full max-w-lg rounded-xl border bg-card p-6 shadow-sm">
|
||
<h1 className="text-xl font-semibold">正在进入智能体模式</h1>
|
||
{status === 'error' && (
|
||
<p className="mt-4 text-sm text-red-600">
|
||
登录失败:{errorMessage || '未知错误'}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|