Bläddra i källkod

纸飞机一键登录

urbanu 1 månad sedan
förälder
incheckning
26c6ba4ef9
4 ändrade filer med 183 tillägg och 97 borttagningar
  1. 2 1
      .gitignore
  2. 1 1
      src/api/auth.ts
  3. 59 77
      src/views/login/index.vue
  4. 121 18
      src/views/register/index.vue

+ 2 - 1
.gitignore

@@ -30,4 +30,5 @@ nul
 *.sln
 *.sw?
 
-/src/typings/components.d.ts
+/src/typings/components.d.ts
+.claude/

+ 1 - 1
src/api/auth.ts

@@ -118,7 +118,7 @@ export function requestOAuthLogin(data: OAuthLoginParams): Promise<CommonRespons
 }
 
 // 获取OAuth配置
-export function requestOAuthConfig(): Promise<CommonResponse<{ googleClientId?: string; zaloAppId?: string; telegramBotName?: string }>> {
+export function requestOAuthConfig(): Promise<CommonResponse<{ googleClientId?: string; zaloAppId?: string; telegramBotName?: string; telegramBotId?: string }>> {
   return http.request({
     url: "/api/v1/dt/config/oauth",
     method: "get"

+ 59 - 77
src/views/login/index.vue

@@ -140,8 +140,13 @@
           <van-loading v-if="googleLoading" size="16px" color="#fff" />
         </div>
 
-        <!-- Telegram 官方 Widget 按钮 -->
-        <div id="telegram-login-container" class="telegram-widget-wrapper" v-if="telegramBotName"></div>
+        <!-- Telegram 登录按钮 -->
+        <div class="oauth-btn telegram-btn" @click="handleTelegramLogin" v-if="telegramBotName">
+          <svg class="telegram-icon" viewBox="0 0 24 24" width="20" height="20">
+            <path fill="#fff" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5.46 7.12l-1.67 6.14c-.13.46-.47.58-.95.36l-2.62-1.93-1.26 1.22c-.14.14-.26.26-.53.26l.19-2.67 4.84-4.37c.21-.19-.05-.29-.32-.12l-5.99 3.78-2.58-.81c-.56-.17-.57-.56.12-.83l10.1-3.9c.46-.17.87.11.67.87z"/>
+          </svg>
+          <span>{{ $t('auth.telegramLogin') || 'Telegram 登录' }}</span>
+        </div>
 
         <!-- Zalo 登录按钮 -->
         <div class="oauth-btn zalo-btn" @click="handleZaloLogin">
@@ -171,7 +176,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
+import { ref, reactive, onMounted, onUnmounted } from 'vue';
 import { useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { toast } from 'vue-sonner';
@@ -185,6 +190,7 @@ const googleLoading = ref(false);
 const googleClientId = ref('');
 const telegramLoading = ref(false);
 const telegramBotName = ref('');
+const telegramBotId = ref('');
 const zaloLoading = ref(false);
 const zaloAppId = ref('');
 
@@ -307,10 +313,7 @@ const initOAuth = async () => {
       // 初始化 Telegram Login
       if (configRes.data.telegramBotName) {
         telegramBotName.value = configRes.data.telegramBotName;
-        // 等待 DOM 更新后再初始化
-        nextTick(() => {
-          initTelegramLogin();
-        });
+        telegramBotId.value = configRes.data.telegramBotId || '';
       }
 
       // 初始化 Zalo Login
@@ -324,35 +327,57 @@ const initOAuth = async () => {
   }
 };
 
-// 初始化 Telegram 登录
-const initTelegramLogin = () => {
-  console.log('initTelegramLogin called, botName:', telegramBotName.value);
+// 点击 Telegram 登录按钮 - 直接打开 Telegram OAuth 弹窗
+const handleTelegramLogin = () => {
+  if (!telegramBotId.value) {
+    toast.error(t('auth.telegramNotConfigured') || 'Telegram 登录未配置');
+    return;
+  }
 
-  // 设置全局回调函数
-  (window as any).onTelegramAuth = (user: any) => {
-    console.log('Telegram auth callback:', user);
-    handleTelegramCallback(user);
+  // 支持传入完整 bot token (如 123456:ABC) 或纯数字 ID
+  const botId = telegramBotId.value.split(':')[0];
+  const origin = encodeURIComponent(window.location.origin);
+  const authUrl = `https://oauth.telegram.org/auth?bot_id=${botId}&origin=${origin}&request_access=write`;
+
+  const width = 550;
+  const height = 470;
+  const left = Math.round((screen.width - width) / 2);
+  const top = Math.round((screen.height - height) / 2);
+
+  const popup = window.open(
+    authUrl,
+    'telegram_oauth',
+    `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no,scrollbars=no`
+  );
+
+  // 监听来自 Telegram OAuth 弹窗的 postMessage
+  const onMessage = (event: MessageEvent) => {
+    if (event.origin !== 'https://oauth.telegram.org') return;
+
+    try {
+      let data = event.data;
+      // Telegram 可能发送 JSON 字符串或对象
+      if (typeof data === 'string') {
+        try { data = JSON.parse(data); } catch { return; }
+      }
+      if (data?.event === 'auth_result' && data?.result) {
+        window.removeEventListener('message', onMessage);
+        handleTelegramCallback(data.result);
+      }
+    } catch (e) {
+      console.error('Telegram auth message error:', e);
+    }
   };
 
-  // 动态创建 Telegram Widget 脚本
-  const container = document.getElementById('telegram-login-container');
-  console.log('Container found:', container);
-
-  if (container) {
-    container.innerHTML = '';
-    const script = document.createElement('script');
-    script.async = true;
-    script.src = 'https://telegram.org/js/telegram-widget.js?22';
-    script.setAttribute('data-telegram-login', telegramBotName.value);
-    script.setAttribute('data-size', 'large');
-    script.setAttribute('data-radius', '8');
-    script.setAttribute('data-onauth', 'onTelegramAuth(user)');
-    script.setAttribute('data-request-access', 'write');
-    script.onload = () => console.log('Telegram widget script loaded');
-    script.onerror = (e) => console.error('Telegram widget script error:', e);
-    container.appendChild(script);
-    console.log('Script appended to container');
-  }
+  window.addEventListener('message', onMessage);
+
+  // 如果弹窗被关闭,清理监听
+  const checkClosed = setInterval(() => {
+    if (popup?.closed) {
+      clearInterval(checkClosed);
+      window.removeEventListener('message', onMessage);
+    }
+  }, 500);
 };
 
 // Telegram 登录回调
@@ -385,38 +410,6 @@ const handleTelegramCallback = async (user: any) => {
   }
 };
 
-// 手动触发 Telegram 登录 - 点击隐藏的 Widget 按钮
-const handleTelegramLogin = () => {
-  if (!telegramBotName.value) {
-    toast.error(t('auth.telegramNotConfigured') || 'Telegram 登录未配置');
-    return;
-  }
-
-  // 查找并点击 Telegram Widget 生成的按钮
-  const container = document.getElementById('telegram-login-container');
-  const widgetBtn = container?.querySelector('iframe');
-
-  if (widgetBtn) {
-    // Widget 已加载,模拟点击
-    widgetBtn.style.position = 'fixed';
-    widgetBtn.style.top = '50%';
-    widgetBtn.style.left = '50%';
-    widgetBtn.style.zIndex = '9999';
-    widgetBtn.click();
-
-    // 稍后隐藏回去
-    setTimeout(() => {
-      widgetBtn.style.position = '';
-      widgetBtn.style.top = '';
-      widgetBtn.style.left = '';
-      widgetBtn.style.zIndex = '';
-    }, 100);
-  } else {
-    // Widget 未加载,重新初始化
-    initTelegramLogin();
-    toast.info('请稍后再试');
-  }
-};
 
 // Google 登录回调处理
 const handleGoogleCallback = async (response: any) => {
@@ -846,6 +839,7 @@ const goRegister = () => {
   }
 }
 
+
 .register-entry {
   text-align: center;
   margin-top: min(5.333vw, 25.6px);
@@ -859,16 +853,4 @@ const goRegister = () => {
   }
 }
 
-.telegram-widget-wrapper {
-  display: flex;
-  justify-content: center;
-  margin-bottom: min(3.2vw, 15.36px);
-  min-height: min(12.8vw, 61.44px);
-
-  iframe {
-    border-radius: min(2.133vw, 10.24px) !important;
-    width: 100% !important;
-    max-width: 100% !important;
-  }
-}
 </style>

+ 121 - 18
src/views/register/index.vue

@@ -228,6 +228,14 @@
           <van-loading v-if="googleLoading" size="16px" color="#fff" />
         </div>
 
+        <!-- Telegram 登录按钮 -->
+        <div class="oauth-btn telegram-btn" @click="handleTelegramLogin" v-if="telegramBotName">
+          <svg class="telegram-icon" viewBox="0 0 24 24" width="20" height="20">
+            <path fill="#fff" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5.46 7.12l-1.67 6.14c-.13.46-.47.58-.95.36l-2.62-1.93-1.26 1.22c-.14.14-.26.26-.53.26l.19-2.67 4.84-4.37c.21-.19-.05-.29-.32-.12l-5.99 3.78-2.58-.81c-.56-.17-.57-.56.12-.83l10.1-3.9c.46-.17.87.11.67.87z"/>
+          </svg>
+          <span>{{ $t('auth.telegramLogin') || 'Telegram 登录' }}</span>
+        </div>
+
         <!-- Zalo 登录按钮 -->
         <div class="oauth-btn zalo-btn" @click="handleZaloLogin">
           <svg class="zalo-icon" viewBox="0 0 24 24" width="20" height="20">
@@ -270,6 +278,8 @@ const route = useRoute();
 const userStore = useUserStore();
 const googleLoading = ref(false);
 const googleClientId = ref('');
+const telegramBotName = ref('');
+const telegramBotId = ref('');
 
 const registerType = ref<'phone' | 'email'>('phone');
 const showPassword = ref(false);
@@ -434,24 +444,31 @@ const showPrivacy = () => {
 const initGoogleSignIn = async () => {
   try {
     const configRes = await requestOAuthConfig();
-    if (configRes.code === 200 && configRes.data.googleClientId) {
-      googleClientId.value = configRes.data.googleClientId;
-
-      const script = document.createElement('script');
-      script.src = 'https://accounts.google.com/gsi/client';
-      script.async = true;
-      script.defer = true;
-      script.onload = () => {
-        if (window.google && googleClientId.value) {
-          window.google.accounts.id.initialize({
-            client_id: googleClientId.value,
-            callback: handleGoogleCallback,
-            auto_select: false,
-            cancel_on_tap_outside: true
-          });
-        }
-      };
-      document.head.appendChild(script);
+    if (configRes.code === 200) {
+      if (configRes.data.googleClientId) {
+        googleClientId.value = configRes.data.googleClientId;
+
+        const script = document.createElement('script');
+        script.src = 'https://accounts.google.com/gsi/client';
+        script.async = true;
+        script.defer = true;
+        script.onload = () => {
+          if (window.google && googleClientId.value) {
+            window.google.accounts.id.initialize({
+              client_id: googleClientId.value,
+              callback: handleGoogleCallback,
+              auto_select: false,
+              cancel_on_tap_outside: true
+            });
+          }
+        };
+        document.head.appendChild(script);
+      }
+
+      if (configRes.data.telegramBotName) {
+        telegramBotName.value = configRes.data.telegramBotName;
+        telegramBotId.value = configRes.data.telegramBotId || '';
+      }
     }
   } catch (error) {
     console.warn('Failed to load OAuth config:', error);
@@ -510,6 +527,82 @@ const handleZaloLogin = () => {
   toast.info('Zalo Login - Coming Soon');
 };
 
+// 初始化 Telegram 登录 - 加载 widget 脚本以获取 Telegram.Login.auth API
+// 点击 Telegram 登录按钮 - 直接打开 Telegram OAuth 弹窗
+const handleTelegramLogin = () => {
+  if (!telegramBotId.value) {
+    toast.error(t('auth.telegramNotConfigured') || 'Telegram 登录未配置');
+    return;
+  }
+
+  // 支持传入完整 bot token (如 123456:ABC) 或纯数字 ID
+  const botId = telegramBotId.value.split(':')[0];
+  const origin = encodeURIComponent(window.location.origin);
+  const authUrl = `https://oauth.telegram.org/auth?bot_id=${botId}&origin=${origin}&request_access=write`;
+
+  const width = 550;
+  const height = 470;
+  const left = Math.round((screen.width - width) / 2);
+  const top = Math.round((screen.height - height) / 2);
+
+  const popup = window.open(
+    authUrl,
+    'telegram_oauth',
+    `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no,scrollbars=no`
+  );
+
+  const onMessage = (event: MessageEvent) => {
+    if (event.origin !== 'https://oauth.telegram.org') return;
+    try {
+      let data = event.data;
+      if (typeof data === 'string') {
+        try { data = JSON.parse(data); } catch { return; }
+      }
+      if (data?.event === 'auth_result' && data?.result) {
+        window.removeEventListener('message', onMessage);
+        handleTelegramCallback(data.result);
+      }
+    } catch (e) {
+      console.error('Telegram auth message error:', e);
+    }
+  };
+
+  window.addEventListener('message', onMessage);
+
+  const checkClosed = setInterval(() => {
+    if (popup?.closed) {
+      clearInterval(checkClosed);
+      window.removeEventListener('message', onMessage);
+    }
+  }, 500);
+};
+
+// Telegram 登录回调
+const handleTelegramCallback = async (user: any) => {
+  try {
+    const inviteCode = phoneForm.inviteCode || emailForm.inviteCode || (route.query.smid as string) || '';
+    const res = await requestOAuthLogin({
+      provider: 'telegram',
+      openId: String(user.id),
+      nickname: [user.first_name, user.last_name].filter(Boolean).join(' '),
+      avatar: user.photo_url || '',
+      extra: JSON.stringify(user),
+      inviteCode
+    });
+
+    if (res.code === 200) {
+      localStorage.setItem('token', res.data.token);
+      userStore.setUserInfo(res.data.user);
+      toast.success(t('auth.loginSuccess'));
+      router.replace('/home');
+    }
+  } catch (error) {
+    console.error('Telegram login error:', error);
+    toast.error(t('auth.telegramLoginFailed') || 'Telegram 登录失败');
+  }
+};
+
+
 onMounted(() => {
   initGoogleSignIn();
 });
@@ -707,6 +800,15 @@ onMounted(() => {
       }
     }
 
+    &.telegram-btn {
+      background: #0088cc;
+      color: #fff;
+
+      &:active {
+        background: #0077b5;
+      }
+    }
+
     &.zalo-btn {
       background: #0068FF;
       color: #fff;
@@ -718,6 +820,7 @@ onMounted(() => {
   }
 }
 
+
 .login-entry {
   text-align: center;
   margin-top: 24px;