From ebbab98a4126729165ebda350a49f2a1e0c26371 Mon Sep 17 00:00:00 2001 From: Mosijiu Date: Fri, 10 Apr 2026 20:38:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=9A=E7=BB=84=E7=BB=87=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(auth)/login/page.tsx | 23 ++++++++--- app/api/auth-settings/route.ts | 8 ++++ app/api/auth/dingtalk/login/route.ts | 6 +-- components/home/lovart-home.tsx | 62 ++++++++++++++++++++++++---- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 9d8c852ce..822376f1d 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -248,11 +248,20 @@ export default function Page() { if (data.isGoogleEnabled) setIsGoogleEnabled(true); if (data.isGoogleConfigured) setIsGoogleConfigured(true); if (data.dingTalkAppKey) setDingTalkAppKey(data.dingTalkAppKey); - if (Array.isArray(data.organizations)) { + const rawOrganizations = Array.isArray(data.organizations) + ? data.organizations + : Array.isArray(data.dingTalkOrganizations) + ? data.dingTalkOrganizations + : []; + + if (rawOrganizations.length > 0) { setDingTalkOrganizations( - data.organizations - .map((org: { name?: string }) => org.name) - .filter((name: string | undefined): name is string => !!name), + rawOrganizations + .map((org: { name?: string } | string) => + typeof org === "string" ? org : org.name, + ) + .filter((name: string | undefined): name is string => Boolean(name?.trim())) + .map((name: string) => name.trim()), ); } if (data.isRegistrationEnabled !== undefined) { @@ -445,8 +454,10 @@ export default function Page() { return; } - // 多组织:点击时展开/收起选择菜单(hover 时也会展开) - setShowOrgMenu((prev) => !prev); + // 多组织:直接打开弹窗中的组织列表,避免悬浮菜单被页面层级覆盖 + setDingTalkIframeSrc(""); + setShowDingTalkModal(true); + setShowOrgMenu(false); }; // 关闭钉钉登录弹窗 diff --git a/app/api/auth-settings/route.ts b/app/api/auth-settings/route.ts index 123ed1ce5..3c0cea6bc 100644 --- a/app/api/auth-settings/route.ts +++ b/app/api/auth-settings/route.ts @@ -36,6 +36,7 @@ export async function GET() { dingTalkAppKey: "", dingTalkAppSecret: "", // 即使是空的,非管理员也不应该看到这个字段名,但因为是空字符串,问题不大 dingTalkOrganizations: [], + organizations: [], isDingTalkEnabled: false, isGoogleEnabled: false, googleClientId: "", @@ -52,6 +53,11 @@ export async function GET() { } if (isAdmin) { + const organizations = + settings.dingTalkOrganizations?.map((org) => ({ + name: org.name, + })) ?? []; + return jsonNoStore({ ...settings, googleClientId: resolvedGoogleClientId, @@ -59,6 +65,7 @@ export async function GET() { authUrl: resolvedAuthUrl, isGoogleConfigured, dingTalkOrganizations: settings.dingTalkOrganizations ?? [], + organizations, isEmailVerificationEnabled: settings.isEmailVerificationEnabled, emailVerificationExpiresMinutes: settings.emailVerificationExpiresMinutes, resendApiKey: settings.resendApiKey || "", @@ -87,6 +94,7 @@ export async function GET() { isEmailServiceConfigured: Boolean( (settings.resendApiKey || "").trim() && (settings.resendFromEmail || "").trim() ), + dingTalkOrganizations: organizations, organizations, // 不返回 dingTalkAppSecret 以及各组织的密钥 }); diff --git a/app/api/auth/dingtalk/login/route.ts b/app/api/auth/dingtalk/login/route.ts index 6e38f19c6..a4f02ce3e 100644 --- a/app/api/auth/dingtalk/login/route.ts +++ b/app/api/auth/dingtalk/login/route.ts @@ -29,9 +29,9 @@ export async function GET(request: Request) { } // 动态构建回调地址 - const host = request.headers.get("host"); - const protocol = request.headers.get("x-forwarded-proto") || "http"; - const baseUrl = `${protocol}://${host}`; + // 优先使用 AUTH_URL 环境变量,确保使用正确的公网域名 + const requestUrl = new URL(request.url); + const baseUrl = process.env.AUTH_URL || requestUrl.origin; const redirectUri = `${baseUrl}/dingtalk-callback.html`; // 使用组织名称作为 state,便于回调时选择对应的配置 diff --git a/components/home/lovart-home.tsx b/components/home/lovart-home.tsx index c26345d43..75d7173a7 100644 --- a/components/home/lovart-home.tsx +++ b/components/home/lovart-home.tsx @@ -139,6 +139,8 @@ type PublicAuthSettings = { isGoogleEnabled?: boolean; isGoogleConfigured?: boolean; isRegistrationEnabled?: boolean; + organizations?: Array<{ name?: string } | string>; + dingTalkOrganizations?: Array<{ name?: string } | string>; }; type ContactConfig = { @@ -362,6 +364,7 @@ export function LovartHome({ const [loginEmail, setLoginEmail] = useState(""); const [loginPassword, setLoginPassword] = useState(""); const [isLoggingIn, setIsLoggingIn] = useState(false); + const [showDingTalkOrgMenu, setShowDingTalkOrgMenu] = useState(false); const [showImageEditor, setShowImageEditor] = useState(false); const [editorImageUrl, setEditorImageUrl] = useState(""); const [tutorialDialogOpen, setTutorialDialogOpen] = useState(false); @@ -663,6 +666,18 @@ export function LovartHome({ const canUseGoogleLogin = Boolean( authSettingsData?.isGoogleEnabled && authSettingsData?.isGoogleConfigured ); + const dingTalkOrganizations = useMemo(() => { + const raw = Array.isArray(authSettingsData?.organizations) + ? authSettingsData.organizations + : Array.isArray(authSettingsData?.dingTalkOrganizations) + ? authSettingsData.dingTalkOrganizations + : []; + + return raw + .map((org) => (typeof org === "string" ? org : org?.name || "")) + .map((name) => name.trim()) + .filter(Boolean); + }, [authSettingsData?.organizations, authSettingsData?.dingTalkOrganizations]); const canUseDingTalkLogin = Boolean(authSettingsData?.isDingTalkEnabled); const canUseRegistration = authSettingsData?.isRegistrationEnabled !== false; const hasThirdPartyLoginOptions = canUseGoogleLogin || canUseDingTalkLogin; @@ -691,6 +706,7 @@ export function LovartHome({ if (!loginDialogOpen) { setIsLoggingIn(false); setLoginPassword(""); + setShowDingTalkOrgMenu(false); } }, [loginDialogOpen]); @@ -903,11 +919,27 @@ export function LovartHome({ await signIn("google", { callbackUrl: "/" }); }; + const startDingTalkLoginForOrg = (orgName: string) => { + setLoginDialogOpen(false); + window.location.href = `/api/auth/dingtalk/login?org=${encodeURIComponent(orgName)}`; + }; + const handleDingTalkLogin = () => { if (!canUseDingTalkLogin) { toast.error("钉钉登录未启用"); return; } + + if (dingTalkOrganizations.length > 1) { + setShowDingTalkOrgMenu((prev) => !prev); + return; + } + + if (dingTalkOrganizations.length === 1) { + startDingTalkLoginForOrg(dingTalkOrganizations[0] as string); + return; + } + setLoginDialogOpen(false); window.location.href = "/api/auth/dingtalk/login"; }; @@ -2122,13 +2154,29 @@ export function LovartHome({ ) : null} {canUseDingTalkLogin ? ( - +
+ + {dingTalkOrganizations.length > 1 && showDingTalkOrgMenu ? ( +
+ {dingTalkOrganizations.map((org) => ( + + ))} +
+ ) : null} +
) : null}