|
|
@@ -125,15 +125,27 @@
|
|
|
<!-- 第三方登录 -->
|
|
|
<div class="third-party-section">
|
|
|
<div class="divider">
|
|
|
- <span>{{ $t('auth.thirdPartyLogin') }}</span>
|
|
|
+ <span>{{ $t('auth.or') || '或' }}</span>
|
|
|
</div>
|
|
|
- <div class="third-party-icons">
|
|
|
- <div class="icon-item google" @click="handleGoogleLogin">
|
|
|
- <van-icon name="passed" />
|
|
|
- </div>
|
|
|
- <div class="icon-item zalo" @click="handleZaloLogin">
|
|
|
- <van-icon name="chat-o" />
|
|
|
- </div>
|
|
|
+
|
|
|
+ <!-- Google 登录按钮 -->
|
|
|
+ <div class="oauth-btn google-btn" :class="{ loading: googleLoading }" @click="handleGoogleLogin">
|
|
|
+ <svg class="google-icon" viewBox="0 0 24 24" width="20" height="20">
|
|
|
+ <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
|
|
+ <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
|
|
+ <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
|
|
+ <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
|
|
+ </svg>
|
|
|
+ <span>{{ $t('auth.googleLogin') || '使用 Google 登录' }}</span>
|
|
|
+ <van-loading v-if="googleLoading" size="16px" color="#fff" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Zalo 登录按钮 -->
|
|
|
+ <div class="oauth-btn zalo-btn" @click="handleZaloLogin">
|
|
|
+ <svg class="zalo-icon" viewBox="0 0 24 24" width="20" height="20">
|
|
|
+ <path fill="#0068FF" 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.zaloLogin') || '使用 Zalo 登录' }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -157,16 +169,18 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive } from 'vue';
|
|
|
+import { ref, reactive, onMounted } from 'vue';
|
|
|
import { useRouter } from 'vue-router';
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
import { toast } from 'vue-sonner';
|
|
|
-import { requestLogin, requestSendCode } from '@/api/auth';
|
|
|
+import { requestLogin, requestSendCode, requestOAuthLogin, requestOAuthConfig } from '@/api/auth';
|
|
|
import { useUserStore } from '@/store/modules/userStore';
|
|
|
|
|
|
const { t } = useI18n();
|
|
|
const router = useRouter();
|
|
|
const userStore = useUserStore();
|
|
|
+const googleLoading = ref(false);
|
|
|
+const googleClientId = ref('');
|
|
|
|
|
|
const loginType = ref<'phone' | 'email'>('phone');
|
|
|
const showPassword = ref(false);
|
|
|
@@ -253,11 +267,103 @@ const handleEmailLogin = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 获取OAuth配置并初始化 Google Sign-In
|
|
|
+const initGoogleSignIn = async () => {
|
|
|
+ try {
|
|
|
+ // 从后端获取OAuth配置
|
|
|
+ const configRes = await requestOAuthConfig();
|
|
|
+ if (configRes.code === 200 && configRes.data.googleClientId) {
|
|
|
+ googleClientId.value = configRes.data.googleClientId;
|
|
|
+
|
|
|
+ // 动态加载 Google Identity Services SDK
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('Failed to load OAuth config:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Google 登录回调处理
|
|
|
+const handleGoogleCallback = async (response: any) => {
|
|
|
+ if (!response.credential) {
|
|
|
+ toast.error('Google 登录失败');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ googleLoading.value = true;
|
|
|
+ try {
|
|
|
+ // 解析 JWT token 获取用户信息
|
|
|
+ const payload = JSON.parse(atob(response.credential.split('.')[1]));
|
|
|
+
|
|
|
+ // 调用后端 OAuth 登录接口
|
|
|
+ const res = await requestOAuthLogin({
|
|
|
+ provider: 'google',
|
|
|
+ openId: payload.sub, // Google 用户唯一 ID
|
|
|
+ nickname: payload.name,
|
|
|
+ avatar: payload.picture,
|
|
|
+ email: payload.email
|
|
|
+ });
|
|
|
+
|
|
|
+ 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('Google login error:', error);
|
|
|
+ toast.error('Google 登录失败');
|
|
|
+ } finally {
|
|
|
+ googleLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
const handleGoogleLogin = () => {
|
|
|
- // TODO: Google OAuth登录
|
|
|
- toast.info('Google Login - Coming Soon');
|
|
|
+ if (!googleClientId.value) {
|
|
|
+ toast.error('Google 登录未配置');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (window.google) {
|
|
|
+ // 使用 Google One Tap 或弹窗登录
|
|
|
+ window.google.accounts.id.prompt((notification: any) => {
|
|
|
+ if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
|
+ // 如果 One Tap 不显示,使用传统弹窗方式
|
|
|
+ window.google.accounts.oauth2.initCodeClient({
|
|
|
+ client_id: googleClientId.value,
|
|
|
+ scope: 'email profile',
|
|
|
+ callback: (response: any) => {
|
|
|
+ if (response.code) {
|
|
|
+ // 这里可以用 code 换取 token,但我们使用 ID Token 方式更简单
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ toast.error('Google SDK 加载失败');
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
+// 组件挂载时初始化 Google Sign-In
|
|
|
+onMounted(() => {
|
|
|
+ initGoogleSignIn();
|
|
|
+});
|
|
|
+
|
|
|
const handleZaloLogin = () => {
|
|
|
// TODO: Zalo OAuth登录
|
|
|
toast.info('Zalo Login - Coming Soon');
|
|
|
@@ -418,33 +524,44 @@ const goRegister = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .third-party-icons {
|
|
|
+ .oauth-btn {
|
|
|
display: flex;
|
|
|
+ align-items: center;
|
|
|
justify-content: center;
|
|
|
- gap: 32px;
|
|
|
+ gap: 10px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
|
- .icon-item {
|
|
|
- width: 48px;
|
|
|
- height: 48px;
|
|
|
- border-radius: 50%;
|
|
|
- background: rgba(255, 255, 255, 0.1);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- cursor: pointer;
|
|
|
+ &.loading {
|
|
|
+ pointer-events: none;
|
|
|
+ opacity: 0.7;
|
|
|
+ }
|
|
|
|
|
|
- .van-icon {
|
|
|
- font-size: 24px;
|
|
|
- }
|
|
|
+ .google-icon, .zalo-icon {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.google-btn {
|
|
|
+ background: #fff;
|
|
|
+ color: #333;
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
- &.google {
|
|
|
- background: rgba(234, 67, 53, 0.2);
|
|
|
- .van-icon { color: #ea4335; }
|
|
|
+ &:active {
|
|
|
+ background: #f5f5f5;
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ &.zalo-btn {
|
|
|
+ background: #0068FF;
|
|
|
+ color: #fff;
|
|
|
|
|
|
- &.zalo {
|
|
|
- background: rgba(0, 136, 204, 0.2);
|
|
|
- .van-icon { color: #0088cc; }
|
|
|
+ &:active {
|
|
|
+ background: #0055cc;
|
|
|
}
|
|
|
}
|
|
|
}
|