From 1d39ff253a8852549f69e39a2a2c942c645f7f04 Mon Sep 17 00:00:00 2001
From: kx_guo
Date: Sun, 13 Sep 2026 15:56:33 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=92=89=E9=92=89=E5=B7=A5?=
=?UTF-8?q?=E4=BD=9C=E5=8F=B0=E8=87=AA=E5=8A=A8=E5=85=8D=E7=99=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/dingtalk.ts | 35 ++++++++++++++++++++++++++++++++---
src/pages/LoginPage.vue | 13 +++++++++----
2 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/src/api/dingtalk.ts b/src/api/dingtalk.ts
index e9b4824..91aeac6 100644
--- a/src/api/dingtalk.ts
+++ b/src/api/dingtalk.ts
@@ -3,14 +3,43 @@ import dd from "dingtalk-jsapi";
import { getDingTalkConfig, loginWithDingTalk } from "@/api/auth";
import type { AuthTokenData } from "@/types/auth";
+declare global {
+ interface Window {
+ dingtalk?: { platform?: { invokeAPI?: unknown } };
+ DingTalkJSBridge?: unknown;
+ }
+}
+
export function isDingTalkContainer() {
- return (
- typeof navigator !== "undefined" && /DingTalk/i.test(navigator.userAgent)
+ if (typeof window === "undefined" || typeof navigator === "undefined")
+ return false;
+ const userAgent =
+ navigator.userAgent ||
+ (navigator as Navigator & { swuserAgent?: string }).swuserAgent ||
+ "";
+ return Boolean(
+ /DingTalk|dingtalk-win|dd-web/i.test(userAgent) ||
+ window.DingTalkJSBridge ||
+ window.dingtalk?.platform?.invokeAPI ||
+ // DingTalk desktop can expose its container identity through window.name
+ // even when the embedded browser omits DingTalk from userAgent.
+ /dingtalk/i.test(window.name || ""),
);
}
+export async function waitForDingTalkContainer(timeoutMs = 5000) {
+ if (isDingTalkContainer()) return true;
+ const startedAt = Date.now();
+ while (Date.now() - startedAt < timeoutMs) {
+ await new Promise((resolve) => window.setTimeout(resolve, 100));
+ if (isDingTalkContainer()) return true;
+ }
+ return false;
+}
+
export async function authenticateWithDingTalk(): Promise {
- if (!isDingTalkContainer()) throw new Error("请从钉钉工作台打开此应用");
+ if (!(await waitForDingTalkContainer()))
+ throw new Error("请从钉钉工作台打开此应用");
const configResponse = await getDingTalkConfig();
const config = configResponse.data.data;
if (!config) throw new Error("钉钉免登配置不完整");
diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue
index 016be24..1d1152b 100644
--- a/src/pages/LoginPage.vue
+++ b/src/pages/LoginPage.vue
@@ -9,7 +9,7 @@ import {
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
-import { isDingTalkContainer } from "@/api/dingtalk";
+import { waitForDingTalkContainer } from "@/api/dingtalk";
import { useAuthStore } from "@/stores/auth.store";
const auth = useAuthStore();
@@ -19,6 +19,7 @@ const useDevelopmentLogin = ref(true);
const username = ref("local-admin");
const password = ref("risk-admin-2026");
const autoDingTalkAttempted = ref(false);
+const autoDingTalk = ref(false);
// 登录后的统一落点是智能对话页,避免从其他受保护页面跳转到登录后
// 又回到原页面,保证用户进入系统后先看到对话工作台。
@@ -49,11 +50,14 @@ async function submitDingTalk() {
}
}
-onMounted(() => {
- if (!isDingTalkContainer() || autoDingTalkAttempted.value) return;
+onMounted(async () => {
+ if (autoDingTalkAttempted.value) return;
+ const inDingTalk = await waitForDingTalkContainer();
+ if (!inDingTalk || autoDingTalkAttempted.value) return;
autoDingTalkAttempted.value = true;
+ autoDingTalk.value = true;
mode.value = "dingtalk";
- void submitDingTalk();
+ await submitDingTalk();
});
@@ -144,6 +148,7 @@ onMounted(() => {
}}