311 lines
7.8 KiB
Vue
311 lines
7.8 KiB
Vue
<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>
|