deotalandAi/apps/frontend/src/components/auth/GoogleOAuthButton.vue

278 lines
6.7 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>
<button
class="google-oauth-button"
:disabled="loading"
@click="handleGoogleLogin"
>
<!-- Google Logo -->
<div class="google-logo">
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<path
fill="#4285F4"
d="M16.51 8.44c0-.62-.05-1.22-.15-1.8H8.98v3.4h4.05c-.17 1-.75 1.85-1.6 2.42v2.01h2.59c1.52-1.4 2.39-3.46 2.39-5.89z"
/>
<path
fill="#34A853"
d="M8.98 17.5c2.43 0 4.47-.8 5.96-2.17l-2.59-2.01c-.8.54-1.83.86-2.99.86-2.3 0-4.24-1.55-4.93-3.64H2.18v2.07C3.99 15.95 6.26 17.5 8.98 17.5z"
/>
<path
fill="#FBBC05"
d="M4.05 10.91c-.15-.54-.23-1.12-.23-1.91s.08-1.37.23-1.91V4.94H2.18C1.55 6.16 1.2 7.55 1.2 9s.35 2.84.98 4.06l2.87-2.15z"
/>
<path
fill="#EA4335"
d="M8.98 4.18c1.17 0 2.22.4 3.06 1.18l2.3-2.3C13.46 1.8 11.41 1.2 8.98 1.2 6.26 1.2 3.99 2.75 2.18 4.94l2.87 2.15c.69-2.09 2.63-3.64 4.93-3.64z"
/>
</svg>
</div>
<!-- 按钮文字 -->
<span class="button-text">
<span v-if="!loading">{{ t('login.google_login') }}</span>
<span v-else>{{ t('login.google_logging') }}</span>
</span>
<!-- 加载状态指示器 -->
<div v-if="loading" class="loading-spinner"></div>
</button>
</template>
<script setup>
import { ref } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { useI18n } from 'vue-i18n'
import { requestUtils,clientApi } from '@deotaland/utils'
const authStore = useAuthStore()
const { t } = useI18n()
const emit = defineEmits(['success', 'error'])
const props = defineProps({
loading: {
type: Boolean,
default: false
}
})
const isProcessing = ref(false)
// 动态加载 Google Identity Services 脚本
const loadGoogleScript = () => {
return new Promise((resolve, reject) => {
if (window.google && window.google.accounts && window.google.accounts.id) {
return resolve()
}
const script = document.createElement('script')
script.src = 'https://accounts.google.com/gsi/client'
script.async = true
script.defer = true
script.onload = () => resolve()
script.onerror = (e) => reject(new Error('Google Identity Services 脚本加载失败'))
document.head.appendChild(script)
})
}
// 处理 Google 登录
const handleGoogleLogin = async () => {
if (isProcessing.value ) return
isProcessing.value = true
await loadGoogleScript()
// const clientId = '680509991778-f5qgqbampabs1atblvm1jkoi4itl1nni.apps.googleusercontent.com'
const clientId = '1087356879940-kcdvstc2pgoim27gffl67qrs297avdmb.apps.googleusercontent.com'
const callback = async (response) => {
console.log(response,'responseresponseresponseresponse');
const idToken = response && response.credential
if (!idToken) {
errorMessage.value = '未获取到 Google 身份凭证'
return
}
await loginWithidToken(idToken)
}
// 初始化并触发 One Tap 或弹出
window.google.accounts.id.initialize({ client_id: clientId, callback })
// 尝试弹出一键登录(如果浏览器允许)
window.google.accounts.id.prompt()
}
const loginWithidToken = async (idToken) => {
try {clientApi
const res = await requestUtils.common(clientApi.default.OAUTH_GOOGLE,{
googleIdToken:idToken
})
if(res.code === 200){
// 登录成功保存token和用户信息
let data = res.data;
authStore.loginSuccess(data,()=>{
emit('success', res.data.user)
})
return
emit('success', res.data.user)
return res
}
return res
} catch (error) {
console.error('登录失败:', error)
emit('error', error)
throw error
} finally {
isProcessing.value = false
}
}
onMounted(() => {
loadGoogleScript()
})
// 注意:在实际项目中,这里需要集成真实的 Google OAuth SDK
// 例如使用 @google-cloud/local-auth 或类似库
// 真实的实现需要:
// 1. 配置 Google OAuth 客户端 ID
// 2. 处理 OAuth 授权流程
// 3. 验证 Google ID Token
// 4. 调用后端 API 创建用户会话
</script>
<style scoped>
/* Google 登录按钮样式 */
.google-oauth-button {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
height: 48px;
background: white;
border: 1px solid #D1D5DB;
border-radius: 12px;
color: #374151;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
box-shadow:
0 1px 2px 0 rgba(0, 0, 0, 0.05),
0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.google-oauth-button:hover:not(:disabled) {
background: #F9FAFB;
border-color: #9CA3AF;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
transform: translateY(-1px);
}
.google-oauth-button:active:not(:disabled) {
transform: translateY(0);
box-shadow:
0 2px 4px -1px rgba(0, 0, 0, 0.1),
0 1px 2px -1px rgba(0, 0, 0, 0.06);
}
.google-oauth-button:focus {
outline: none;
ring: 2px solid #6B46C1;
ring-offset: 2px;
ring-offset-color: white;
}
.google-oauth-button:disabled {
background: #F3F4F6;
border-color: #E5E7EB;
color: #9CA3AF;
cursor: not-allowed;
transform: none;
}
/* Google Logo */
.google-logo {
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
flex-shrink: 0;
}
/* 按钮文字 */
.button-text {
flex: 1;
text-align: center;
font-weight: 500;
letter-spacing: 0.025em;
}
/* 加载状态指示器 */
.loading-spinner {
position: absolute;
right: 16px;
width: 18px;
height: 18px;
border: 2px solid transparent;
border-top: 2px solid #6B7280;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* 按钮激活状态动画 */
.google-oauth-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(139, 92, 246, 0.1), transparent);
transition: left 0.5s ease;
}
.google-oauth-button:hover::before {
left: 100%;
}
/* 响应式设计 */
@media (max-width: 768px) {
.google-oauth-button {
height: 44px;
font-size: 15px;
}
}
@media (max-width: 480px) {
.google-oauth-button {
height: 42px;
font-size: 14px;
}
}
.google-oauth-button:hover::before {
left: 100%;
}
/* 移动端优化 */
@media (max-width: 768px) {
.google-oauth-button {
height: 44px;
font-size: 15px;
}
.button-text {
font-size: 15px;
}
}
@media (max-width: 480px) {
.google-oauth-button {
height: 42px;
font-size: 14px;
gap: 10px;
}
.google-logo {
width: 16px;
height: 16px;
}
}
</style>