Files
lanecarford_front/src/views/login/components/GoogleLogin.vue
2025-11-05 10:24:53 +08:00

311 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="Container" @click="handleContainerClick">
<div class="g_id_signin" id="g_id_signin"></div>
<div class="icon">
<img :src="google" class="google-icon" />
<span>{{ text }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted, onBeforeUnmount } from 'vue'
import { showToast } from 'vant'
import { google } from '@/assets/base64'
// 定义 props
defineProps({
text: {
type: String,
default: 'Sign in with Google'
}
})
// 定义 emits
const emit = defineEmits<{
(e: 'googelLogin', code: string): void
}>()
// JWT 解码函数
function decodeJWT(token: string) {
const base64Url = token.split('.')[1]
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
// 使用类型断言来避免 deprecated 警告atob 在浏览器环境中仍然是最佳实践
const decoded = (atob as (data: string) => string)(base64)
const jsonPayload = decodeURIComponent(
decoded
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
})
.join('')
)
return JSON.parse(jsonPayload)
}
// 处理 Google 登录回调
const handleCredentialResponse = async (response: { credential: string }) => {
// 获取回调响应的凭证数据 然后拿这个凭证给后台后台jwt进行解析获取登录信息
console.log('Encoded JWT ID token: ' + response.credential)
const responsePayload = decodeJWT(response.credential)
console.log('Decoded JWT ID token fields:')
console.log(' Full Name: ' + responsePayload.name)
console.log(' Given Name: ' + responsePayload.given_name)
console.log(' Family Name: ' + responsePayload.family_name)
console.log(' Unique ID: ' + responsePayload.sub)
console.log(' Profile image URL: ' + responsePayload.picture)
console.log(' Email: ' + responsePayload.email)
const code = response.credential
emit('googelLogin', code)
// 移除这行,保持按钮可以重复使用
// window.isAddGmail = false
}
// 配置数据
const data = reactive({
// scriptSrc:'https://apis.google.com/js/platform.js',
scriptSrc: 'https://accounts.google.com/gsi/client',
script: null
})
console.log(import.meta.env.VITE_USER_NODE_ENV)
// 根据环境设置 Google Client ID
const GOOGLE_CLIENT_ID = '216037134725-7q8vqp0ohtmohlosltkfg7bd2v29rm5a.apps.googleusercontent.com'
// 等待 Google API 可用
const waitForGoogle = (): Promise<void> => {
return new Promise((resolve) => {
const google = window.google as any
if (google?.accounts?.id) {
resolve()
return
}
const checkInterval = setInterval(() => {
if (google?.accounts?.id) {
clearInterval(checkInterval)
resolve()
}
}, 50)
// 10秒超时
setTimeout(() => {
clearInterval(checkInterval)
resolve()
}, 10000)
})
}
// 初始化 Google API确保脚本加载和 API 可用)
const initializeGoogleAPI = async () => {
const existingScript = document.querySelector(`script[src="${data.scriptSrc}"]`)
if (!existingScript) {
window.isAddGmail = true
await new Promise<void>((resolve) => {
const script = document.createElement('script')
script.onload = () => {
resolve()
}
script.onerror = () => {
console.error('Failed to load Google script')
resolve() // 仍然 resolve让后续代码处理
}
document.body.appendChild(script)
script.src = data.scriptSrc
})
}
// 等待 Google API 完全加载
await waitForGoogle()
const google = window.google as any
if (!google?.accounts?.id) {
console.error('Google API not available')
return false
}
return true
}
// 重新初始化 Google Sign-In每次调用时都重新初始化确保状态正确
const reinitializeGoogleSignIn = () => {
const google = window.google as any
if (!google?.accounts?.id) {
return false
}
// 每次重新初始化,确保状态正确
google.accounts.id.initialize({
client_id: GOOGLE_CLIENT_ID,
auto_select: false,
callback: handleCredentialResponse,
ux_mode: 'popup',
itp_support: true
})
return true
}
// 创建 Gmail 登录按钮
const createGmailLogin = async () => {
const isReady = await initializeGoogleAPI()
if (!isReady) {
return
}
// 重新初始化确保状态正确
reinitializeGoogleSignIn()
// 等待一小段时间确保初始化完成
await new Promise(resolve => setTimeout(resolve, 100))
const google = window.google as any
// 然后渲染按钮
const buttonElement = document.querySelector('.Container #g_id_signin')
if (buttonElement) {
// 清除之前的内容,重新渲染
buttonElement.innerHTML = ''
google.accounts.id.renderButton(buttonElement, {
type: 'standard',
shape: 'circle',
theme: 'outline',
size: 'large',
logo_alignment: 'center'
})
} else {
console.error('Button element not found')
}
}
// 手动触发 Google 登录(用于处理弹窗关闭后无法再次触发的问题)
const triggerGoogleLogin = async () => {
const isReady = await initializeGoogleAPI()
if (!isReady) {
return
}
// 重新初始化确保状态正确
reinitializeGoogleSignIn()
const google = window.google as any
// 使用 prompt() 方法手动触发登录
try {
google.accounts.id.prompt((notification: any) => {
// 处理通知(例如用户关闭了弹窗)
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
// 如果提示没有显示或被跳过,可以尝试使用按钮
console.log('Prompt not displayed, trying button click')
}
})
} catch (error) {
console.error('Error triggering Google login:', error)
// 如果 prompt() 失败,尝试重新渲染按钮
await createGmailLogin()
}
}
const toGmailLogin = () => {
showToast('Google login is not available yet')
}
// 处理容器点击事件(确保每次点击都能触发登录)
let isProcessingClick = false
const handleContainerClick = async () => {
// 防止重复点击
if (isProcessingClick) {
return
}
isProcessingClick = true
// 确保 API 已加载
const isReady = await initializeGoogleAPI()
if (isReady) {
// 重新初始化 API确保状态正确不重新渲染按钮
reinitializeGoogleSignIn()
}
// 短暂延迟后重置标志,允许下次点击
setTimeout(() => {
isProcessingClick = false
}, 200)
}
onBeforeUnmount(() => {
const existingScript = document.querySelector(`script[src="${data.scriptSrc}"]`)
if (existingScript) {
existingScript.remove()
window.isAddGmail = false
}
})
onMounted(() => {
createGmailLogin()
})
</script>
<style scoped lang="less">
.Container {
width: 100%;
height: 10.6rem;
padding: 1.6rem;
border: 0.2rem solid #fff;
border-radius: 7rem;
font-size: 3.83rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
column-gap: 3.1rem;
margin-bottom: 1.67rem;
:deep(.nsm7Bb-HzV7m-LgbsSe-bN97Pc-sM5MNb) {
justify-content: center;
.nsm7Bb-HzV7m-LgbsSe-Bz112c {
padding: 0;
}
}
:deep(.nsm7Bb-HzV7m-LgbsSe.JGcpL-RbRzK) {
width: 100%;
height: 10.6rem;
}
.icon {
width: auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4rem;
padding: 1.3rem 4.6rem;
cursor: pointer;
box-sizing: border-box;
position: relative;
background: transparent;
pointer-events: none;
column-gap: 3.1rem;
}
.g_id_signin {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0;
:deep(.S9gUrf-YoZ4jf) {
&,
& > div {
width: 100%;
height: 100%;
}
iframe {
zoom: 3;
}
}
}
}
.google-icon {
width: 4.8rem;
height: 4.8rem;
}
</style>