多组织选择修复
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Mosijiu 2026-04-10 20:38:54 +08:00
parent d450bec1d7
commit ebbab98a41
4 changed files with 83 additions and 16 deletions

View File

@ -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);
};
// 关闭钉钉登录弹窗

View File

@ -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 以及各组织的密钥
});

View File

@ -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,便于回调时选择对应的配置

View File

@ -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({
</button>
) : null}
{canUseDingTalkLogin ? (
<button
type="button"
className="flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-black/10 text-[15px] font-medium text-black hover:bg-black/[0.03]"
onClick={handleDingTalkLogin}
>
使用钉钉
</button>
<div className="relative">
<button
type="button"
className="flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-black/10 text-[15px] font-medium text-black hover:bg-black/[0.03]"
onClick={handleDingTalkLogin}
>
使用钉钉
</button>
{dingTalkOrganizations.length > 1 && showDingTalkOrgMenu ? (
<div className="absolute left-0 right-0 top-full z-30 mt-2 max-h-60 overflow-auto rounded-xl border border-black/10 bg-white p-1.5 shadow-xl">
{dingTalkOrganizations.map((org) => (
<button
key={org}
type="button"
className="flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-[14px] text-black hover:bg-black/[0.04]"
onClick={() => startDingTalkLoginForOrg(org)}
>
<span>{org}</span>
</button>
))}
</div>
) : null}
</div>
) : null}
<div className="my-2 border-t border-black/10" />
</div>