Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
boyueCEnd
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
游洁
boyueCEnd
Commits
7b04b7f8
Commit
7b04b7f8
authored
Nov 20, 2025
by
youjie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
授权
parent
d4462fc9
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
685 additions
and
90 deletions
+685
-90
index.html
index.html
+3
-0
zh-CN.ts
src/i18n/locales/zh-CN.ts
+1
-0
index.ts
src/router/index.ts
+6
-0
UserService.ts
src/services/UserService.ts
+23
-2
user.ts
src/stores/user.ts
+4
-4
Login.vue
src/views/auth/Login.vue
+96
-83
header.vue
src/views/auth/components/header.vue
+1
-1
register2.vue
src/views/auth/register2.vue
+551
-0
No files found.
index.html
View file @
7b04b7f8
...
...
@@ -4,6 +4,9 @@
<meta
charset=
"UTF-8"
/>
<link
rel=
"icon"
href=
"/favicon.ico"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<script
src=
"https://unpkg.com/vue@3/dist/vue.global.js"
></script>
<link
rel=
"stylesheet"
href=
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
>
<meta
http-equiv=
"Cross-Origin-Opener-Policy"
content=
"same-origin-allow-popups"
>
<title>
C-end
</title>
</head>
<body>
...
...
src/i18n/locales/zh-CN.ts
View file @
7b04b7f8
...
...
@@ -90,6 +90,7 @@ export default {
phoneCode
:
'区号'
,
googleLoginFailed
:
'谷歌登录失败'
,
isReceivePush
:
'「我同意接收优惠与电子报」'
,
loginTypeNotSupport
:
'暂未实现该登录方式'
,
},
common
:
{
language
:
'语言'
,
...
...
src/router/index.ts
View file @
7b04b7f8
...
...
@@ -23,6 +23,12 @@ const router = createRouter({
component
:
()
=>
import
(
"../views/auth/register.vue"
),
meta
:
{
title
:
"login.register"
},
},
{
path
:
"/login2"
,
name
:
"login2"
,
component
:
()
=>
import
(
"../views/auth/register2.vue"
),
meta
:
{
title
:
"login.register"
},
},
{
path
:
"/forgePassword"
,
name
:
"forgePassword"
,
...
...
src/services/UserService.ts
View file @
7b04b7f8
import
OtaRequest
from
'@/api/OtaRequest'
import
OtaRequest
,{
type
HttpResponse
}
from
'@/api/OtaRequest'
/**
* 用户服务 - 处理所有用户相关的 API 请求
...
...
@@ -395,7 +395,7 @@ export interface ResetPasswordResponseDto {
*/
class
UserService
{
/**
*
代理商自助
注册
* 注册
* @param data 注册信息
* @returns 注册响应
*/
...
...
@@ -446,6 +446,27 @@ class UserService {
)
return
response
as
unknown
as
DistributorLoginResultDto
}
/**
* 谷歌登录
* @param credential 谷歌token
* @returns
*/
static
async
GoogleLoginAsync
(
credential
:
string
):
Promise
<
HttpResponse
>
{
const
data
=
{
credential
}
// OtaRequest 的响应拦截器会返回 response.data
const
response
=
await
OtaRequest
.
post
(
'/member-auth/google-auth-bind'
,
data
,
{
headers
:
{
}
}
)
return
response
as
unknown
as
HttpResponse
}
/**
* 刷新访问令牌
...
...
src/stores/user.ts
View file @
7b04b7f8
...
...
@@ -111,8 +111,8 @@ export const useUserStore = defineStore('user', {
*/
async
setUserGoogleLoginAsync
(
credential
:
string
):
Promise
<
UserLoginResult
>
{
try
{
const
response
=
await
Erp
UserService
.
GoogleLoginAsync
(
credential
)
const
response
=
await
UserService
.
GoogleLoginAsync
(
credential
)
console
.
log
(
'Google login response:'
,
response
)
if
(
response
.
data
.
resultCode
===
ApiResult
.
SUCCESS
)
{
this
.
token
=
response
.
data
.
data
.
token
||
''
this
.
userInfo
=
response
.
data
.
data
||
{}
...
...
@@ -124,7 +124,7 @@ export const useUserStore = defineStore('user', {
data
:
response
.
data
.
data
}
}
else
{
ResultMessage
.
Error
(
response
.
data
.
message
||
i18n
.
global
.
t
(
'login.googleLoginFailed'
)
)
ResultMessage
.
Error
(
response
.
data
.
message
||
'谷歌登录失败'
)
return
{
status
:
'ERROR'
,
verify
:
false
...
...
@@ -132,7 +132,7 @@ export const useUserStore = defineStore('user', {
}
}
catch
(
error
:
any
)
{
console
.
error
(
'Google login error:'
,
error
)
ResultMessage
.
Error
(
error
.
message
||
i18n
.
global
.
t
(
'login.googleLoginFailed'
)
)
ResultMessage
.
Error
(
error
.
message
||
'谷歌登录失败'
)
return
{
status
:
'ERROR'
,
verify
:
false
...
...
src/views/auth/Login.vue
View file @
7b04b7f8
...
...
@@ -3,30 +3,20 @@
<div
ref=
"loginPage"
class=
"light-login-bg pl-[85px] pr-[98px] pt-[33px] h-full !overflow-y-auto light-login-bg"
>
<loginHeader
/>
<!--
<div
class=
"w-full relative h-[125px] mt-[32px] mb-[44px]"
>
<div
class=
"absolute left-0 right-0 top-0 flex justify-center items-center"
>
<img
src=
"../../assets/images/login-logo.png"
alt=
""
class=
"h-[68px]"
/>
</div>
<div
class=
"absolute left-0 right-0 bottom-0 flex justify-center items-center"
>
<div
class=
"text-[27px] primary6 SourceHanSansCN"
>
{{
t
(
'login.subtitle'
)
}}
</div>
</div>
</div>
-->
<div
class=
"w-full flex flex-col loginForm pt-[97px]"
>
<div
class=
"flex justify-center"
>
<div
class=
"w-[463px] h-[620px] "
>
<!--
<div
class=
"loginForm-bg w-full h-[620px] rounded-[18px] absolute top-0 left-0 bottom-0 z-[2]"
></div>
-->
<!-- absolute top-0 left-0 bottom-0 -->
<div
class=
"w-[463px] h-[620px]"
>
<div
class=
"loginForm-bg w-full h-full rounded-[18px] flex flex-col"
>
<div
class=
"text-center pt-[46px] primary-6"
>
<div
class=
"text-[32px] font-bold"
>
{{
t
(
'login.loginTo'
)
}}
</div>
<!--
<div
class=
"text-[17px] mt-[18px]"
>
{{
t
(
'login.loginToSubImm'
)
}}
</div>
-->
<div
class=
"flex justify-center items-center mt-[18px]"
>
<img
src=
"@/assets/images/welcome-login.png"
alt=
""
class=
"h-[26px]"
>
</div>
</div>
<a-space
direction=
"vertical"
class=
"px-[72px]"
>
<a-form
:model=
"loginMsg"
:rules=
"rules"
@
submit=
"loginHandler"
layout=
"vertical"
class=
"mt-[42px]"
>
class=
"mt-[42px]"
:disabled=
"loginMsg.reType!==0"
>
<a-form-item
field=
"email"
:label=
"t('login.account')"
>
<a-input
class=
"loginMsg-input"
v-model=
"loginMsg.email"
...
...
@@ -53,7 +43,7 @@
</a-input-password>
</a-form-item>
<div
class=
"mt-[34px] flex flex-row items-center items-center-button"
<div
v-if=
"loginMsg.reType==0"
class=
"mt-[34px] flex flex-row items-center items-center-button"
:class=
"[loginMsg.password&&loginMsg.password.length>=8&&loginMsg.email?'isClick':'']"
>
<a-button
type=
"primary"
...
...
@@ -68,35 +58,18 @@
</a-form>
</a-space>
<!-- 谷歌登录 -->
<!-- <div v-else-if="loginMsg.reType === 1" class="login-form-content google-content">
<div class="google-auth-container">
<div
id="g_id_onload"
data-client_id="13534363185-3utcasahjr950mf6uumq8upefl0fu2rl.apps.googleusercontent.com"
data-context="signin"
data-ux_mode="popup"
data-callback="googleCallback"
data-auto_select="false"
data-itp_support="true"
></div>
<div
class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-locale="en-US"
data-logo_alignment="center"
data-width="360"
></div>
</div>
</div> -->
<div
v-show=
"loginMsg.reType==1"
class=
"px-[72px] mt-[36px]"
>
<div
ref=
"googleButtonContainer"
class=
"g-signin2 !rounded-[13px] overflow-hidden"
data-onsuccess=
"onSignIn"
data-theme=
"dark"
></div>
</div>
<!-- Line登录 -->
<
!-- <div v-else-if="loginMsg.reType ===
3" class="login-form-content scan-content">
<
div
v-if=
"loginMsg.reType==
3"
class=
"login-form-content scan-content"
>
<div
class=
"qr-container"
>
<div
class=
"qr-box line-qr-box"
>
<div
class=
"qr-code-placeholder"
>
...
...
@@ -106,14 +79,14 @@
<p
class=
"scan-instruction"
>
{{ t('login.scanTip') }}
</p>
<p
class=
"scan-status"
>
{{ t('login.scanWaiting') }}
</p>
</div>
</div>
-->
</div>
<div
class=
"mt-[40px] flex items-center justify-center"
>
<a-divider
orientation=
"center"
class=
"text-[16px] text-[#EEEFEB]"
></a-divider>
<span
class=
"text-nowrap primary1-3 px-[14px]"
>
{{ t('login.othenLogin') }}
</span>
<a-divider
orientation=
"center"
class=
"text-[16px] text-[#EEEFEB]"
></a-divider>
</div>
<div
class=
"flex items-center justify-between px-[100px] mt-[20px]"
>
<div
class=
"flex items-center justify-between px-[100px] mt-[20px]
mb-[40px]
"
>
<!-- loginForm-itemActive loginForm-item-->
<div
class=
"w-[42px] h-[42px]
rounded-full bg-[#FFF]
...
...
@@ -136,13 +109,14 @@
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
computed
}
from
"vue"
;
import
{
ref
,
reactive
,
computed
,
onMounted
}
from
"vue"
;
import
{
useI18n
}
from
"vue-i18n"
;
import
{
useRouter
}
from
'vue-router'
import
{
useUserStore
}
from
'@/stores/index'
import
{
useSystemConfigStore
}
from
'@/stores/index'
import
ErpUserService
from
'@/services/ErpUserService'
import
{
ApiResult
}
from
'@/types/ApiResult'
import
{
Message
}
from
'@arco-design/web-vue'
import
loginHeader
from
"./components/header.vue"
;
import
f
from
'@/assets/images/login_f.png'
import
G
from
'@/assets/images/login_G.png'
...
...
@@ -151,17 +125,15 @@ import line from '@/assets/images/login_line.png'
const
{
t
}
=
useI18n
();
const
{
t
,
locale
}
=
useI18n
();
const
userStore
=
useUserStore
()
const
systemConfigStore
=
useSystemConfigStore
()
const
loading
=
ref
(
false
)
const
currentTenantId
=
ref
(
0
)
const
needVerify
=
ref
(
false
)
const
validateToken
=
ref
(
''
)
const
router
=
useRouter
()
const
invisibleHcaptcha
=
ref
()
const
googleButtonContainer
=
ref
(
null
);
const
loginMsg
=
reactive
({
tenantId
:
systemConfigStore
.
tenantId
||
null
,
...
...
@@ -228,14 +200,72 @@ const handleClick = (path: string) => {
}
const
toggleLoginType
=
(
type
:
number
)
=>
{
if
(
type
>
1
)
return
Message
.
error
(
t
(
'login.loginTypeNotSupport'
))
loginMsg
.
reType
=
type
if
(
type
==
1
)
{
initGoogleLogin
()
}
}
const
loginHandler
=
async
({
values
,
errors
}:
any
)
=>
{
if
(
errors
||
loading
.
value
)
return
await
handleLogin
()
}
// 渲染 Google 登录按钮
const
renderGoogleButton
=
()
=>
{
console
.
log
(
'渲染 Google 登录按钮...'
);
try
{
// 渲染 Google 登录按钮
window
.
google
.
accounts
.
id
.
initialize
({
client_id
:
'532164762940-vk65sge5jab1eq8mgbv1srh672ehnkff.apps.googleusercontent.com'
,
callback
:
handleSignInSuccess
,
error_callback
:
handleSignInError
,
ux_mode
:
'popup'
// 可选:popup/redirect
});
window
.
google
.
accounts
.
id
.
renderButton
(
googleButtonContainer
.
value
,
{
theme
:
'outline'
,
size
:
'large'
}
);
window
.
google
.
accounts
.
id
.
configure
({
language
:
locale
.
value
// 支持的语言代码
});
}
catch
(
error
)
{
console
.
error
(
'Error rendering Google Sign-In button:'
,
error
);
}
};
// Google 登录回调
const
handleSignInSuccess
=
(
googleUser
:
any
)
=>
{
console
.
log
(
'Google 登录成功:'
,
googleUser
);
// 获取授权码
const
authResponse
=
googleUser
.
getAuthResponse
();
// 实际项目中应发送到后端
console
.
log
(
'授权码:'
,
authResponse
);
// 更新状态
};
// 处理登录错误
const
handleSignInError
=
(
error
)
=>
{
console
.
error
(
'登录错误:'
,
error
);
};
// 初始化谷歌登录
const
initGoogleSDK
=
async
()
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
// 动态加载 Google SDK
const
script
=
document
.
createElement
(
'script'
);
script
.
src
=
'https://accounts.google.com/gsi/client'
;
script
.
async
=
true
;
script
.
defer
=
true
;
script
.
onload
=
resolve
;
script
.
onerror
=
reject
;
document
.
head
.
appendChild
(
script
);
});
};
const
handleLogin
=
async
()
=>
{
loading
.
value
=
true
try
{
const
result
=
await
userStore
.
setUserPasswordLoginAsync
(
...
...
@@ -246,47 +276,16 @@ const loginHandler = async ({ values, errors }: any) => {
loginMsg
.
openId
?.
toString
()
||
''
,
)
loading
.
value
=
false
currentTenantId
.
value
=
0
if
(
result
.
status
==
'SUCCESS'
)
{
const
forward
=
localStorage
.
getItem
(
'forward'
)
localStorage
.
removeItem
(
'forward'
)
router
.
push
({
path
:
forward
?
forward
:
'/'
,
})
}
else
{
if
(
!
needVerify
.
value
)
needVerify
.
value
=
result
.
verify
if
(
invisibleHcaptcha
.
value
&&
needVerify
.
value
)
invisibleHcaptcha
.
value
.
reset
()
}
}
finally
{
loading
.
value
=
false
}
}
// 初始化谷歌登录
const
initGoogleLogin
=
()
=>
{
const
cb
=
async
(
data
:
any
)
=>
{
loading
.
value
=
true
try
{
const
result
=
await
userStore
.
setUserGoogleLoginAsync
(
data
.
credential
)
if
(
result
.
status
===
'SUCCESS'
)
{
// 登录成功,跳转到Dashboard
const
forward
=
localStorage
.
getItem
(
'forward'
)
localStorage
.
removeItem
(
'forward'
)
router
.
push
({
path
:
forward
||
'/dashboard'
,
})
}
}
finally
{
loading
.
value
=
false
}
}
;(
window
as
any
).
googleCallback
=
cb
const
script
=
document
.
createElement
(
'script'
)
script
.
src
=
'https://accounts.google.com/gsi/client'
document
.
body
.
appendChild
(
script
)
}
// 检查是否需要验证
...
...
@@ -319,12 +318,21 @@ const init = async () => {
}
await
checkNeedVerify
()
// initGoogleLogin()
}
init
()
onMounted
(
async
()
=>
{
try
{
await
initGoogleSDK
();
// 确保 SDK 加载完成后渲染按钮
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
1000
));
renderGoogleButton
()
}
catch
(
error
)
{
console
.
error
(
'SDK 初始化失败:'
,
error
);
}
});
</
script
>
<
style
scoped
lang=
"scss"
>
:deep
(
.arco-form-item-content
)
{
...
...
@@ -390,6 +398,11 @@ init()
:deep
(
.arco-form-item-message
)
{
color
:
rgba
(
255
,
0
,
0
,
0
);
}
:deep
(
.nsm7Bb-HzV7m-LgbsSe
)
{
border-radius
:
13px
;
height
:
44px
;
padding
:
0
17px
;
}
.light-login-bg
{
background
:
url('../../assets/images/login-bg.png')
no-repeat
;
background-size
:
100%
100%
;
...
...
src/views/auth/components/header.vue
View file @
7b04b7f8
...
...
@@ -34,7 +34,7 @@ import { useRouter } from 'vue-router'
const
{
t
}
=
useI18n
();
const
systemConfigStore
=
useSystemConfigStore
()
const
currentStep
=
ref
(
inject
(
'currentStep'
))
const
currentStep
=
ref
(
inject
(
'currentStep'
)
??
0
)
const
router
=
useRouter
()
const
goHome
=
(
path
:
string
)
=>
{
...
...
src/views/auth/register2.vue
0 → 100644
View file @
7b04b7f8
<
template
>
<div
class=
"card"
>
<div
class=
"card-header"
>
<h2>
Google 登录演示
</h2>
</div>
<div
class=
"card-body"
>
<div
class=
"login-section"
>
<div
class=
"error-message"
v-if=
"showError"
>
<i
class=
"fas fa-exclamation-circle error-icon"
></i>
<div>
<h3>
Google SDK 加载失败
</h3>
<p>
{{
errorMessage
}}
</p>
</div>
</div>
<div
class=
"google-btn-container"
>
<button
id=
"google-login-btn"
class=
"google-btn"
@
click=
"initGoogleLogin"
:disabled=
"loading"
>
<i
class=
"fab fa-google"
></i>
<span
v-if=
"!loading"
>
使用 Google 账号登录
</span>
<span
v-else
><span
class=
"loading-spinner"
></span>
加载中...
</span>
</button>
</div>
<div
class=
"status-panel"
>
<h3>
登录状态
</h3>
<div
class=
"status-content"
>
{{
statusLog
}}
</div>
<div
v-if=
"user"
class=
"user-info"
>
<img
:src=
"user.picture"
alt=
"用户头像"
class=
"avatar"
>
<div
class=
"user-details"
>
<h4>
{{
user
.
name
}}
</h4>
<p>
{{
user
.
email
}}
</p>
</div>
</div>
</div>
<div
style=
"display: flex; gap: 10px; margin-top: 20px;"
>
<button
class=
"btn"
@
click=
"simulateSuccess"
>
<i
class=
"fas fa-check-circle"
></i>
模拟成功登录
</button>
<button
class=
"btn btn-outline"
@
click=
"resetDemo"
>
<i
class=
"fas fa-redo"
></i>
重置演示
</button>
</div>
</div>
</div>
</div>
<footer>
<p>
© 2023 Vue3 Google 登录解决方案 | 安全可靠的企业级认证集成
</p>
</footer>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
computed
,
onMounted
}
from
"vue"
;
const
statusLog
=
ref
(
'等待用户操作...
\
n'
);
const
user
=
ref
(
null
);
const
loading
=
ref
(
false
);
const
showError
=
ref
(
false
);
const
errorMessage
=
ref
(
''
);
// 添加日志
const
addLog
=
(
message
)
=>
{
statusLog
.
value
+=
`[
${
new
Date
().
toLocaleTimeString
()}
]
${
message
}
\n`
;
};
// 检查 Google SDK 是否加载
const
checkGoogleSDK
=
()
=>
{
addLog
(
'检查 Google SDK 状态...'
);
if
(
window
.
google
&&
window
.
google
.
accounts
&&
window
.
google
.
accounts
.
id
)
{
addLog
(
'✅ Google SDK 已成功加载'
);
addLog
(
`SDK 版本:
${
window
.
google
.
version
||
'未知'
}
`
);
showError
.
value
=
false
;
}
else
{
addLog
(
'❌ Google SDK 未加载'
);
errorMessage
.
value
=
'Google Identity Services SDK 未能正确加载。请检查网络连接或域名授权设置。'
;
showError
.
value
=
true
;
}
};
// 初始化 Google 登录
const
initGoogleLogin
=
()
=>
{
addLog
(
'开始初始化 Google 登录...'
);
loading
.
value
=
true
;
showError
.
value
=
false
;
// 检查 SDK 是否已加载
if
(
!
window
.
google
||
!
window
.
google
.
accounts
||
!
window
.
google
.
accounts
.
id
)
{
addLog
(
'⚠️ Google SDK 未加载,尝试动态加载...'
);
// 动态加载 Google SDK
const
script
=
document
.
createElement
(
'script'
);
script
.
src
=
'https://accounts.google.com/gsi/client'
;
script
.
async
=
true
;
script
.
defer
=
true
;
script
.
onload
=
()
=>
{
addLog
(
'✅ Google SDK 动态加载成功'
);
renderGoogleButton
();
};
script
.
onerror
=
()
=>
{
addLog
(
'❌ Google SDK 动态加载失败'
);
errorMessage
.
value
=
'无法加载 Google Identity Services SDK。请检查网络连接或防火墙设置。'
;
showError
.
value
=
true
;
loading
.
value
=
false
;
};
document
.
head
.
appendChild
(
script
);
return
;
}
// SDK 已加载,直接渲染按钮
renderGoogleButton
();
};
// 渲染 Google 登录按钮
const
renderGoogleButton
=
()
=>
{
addLog
(
'渲染 Google 登录按钮...'
);
try
{
// 使用 Google 官方 API 渲染按钮
window
.
google
.
accounts
.
id
.
initialize
({
client_id
:
'532164762940-vk65sge5jab1eq8mgbv1srh672ehnkff.apps.googleusercontent.com'
,
// 示例ID,需替换
callback
:
handleGoogleSignIn
,
auto_select
:
false
,
cancel_on_tap_outside
:
false
,
context
:
'signin'
,
ux_mode
:
'popup'
,
});
// 清除现有按钮(如果有)
const
container
=
document
.
getElementById
(
'google-login-btn'
);
container
.
innerHTML
=
''
;
// 渲染新按钮
window
.
google
.
accounts
.
id
.
renderButton
(
container
,
{
theme
:
'outline'
,
size
:
'large'
,
text
:
'signin_with'
,
shape
:
'rectangular'
,
logo_alignment
:
'left'
,
width
:
'100%'
}
);
addLog
(
'✅ Google 登录按钮渲染成功'
);
addLog
(
'请点击按钮继续登录流程'
);
loading
.
value
=
false
;
}
catch
(
error
)
{
addLog
(
`❌ 渲染 Google 按钮失败:
${
error
.
message
}
`
);
errorMessage
.
value
=
`渲染 Google 登录按钮时出错:
${
error
.
message
}
`
;
showError
.
value
=
true
;
loading
.
value
=
false
;
}
};
// Google 登录回调
const
handleGoogleSignIn
=
async
(
response
)
=>
{
// clientId client_id credential select_by
console
.
log
(
response
.
credential
,
'----------'
);
addLog
(
'Google 登录回调触发'
);
addLog
(
`收到凭证:
${
response
.
credential
.
substring
(
0
,
30
)}
...`
);
try
{
// 模拟发送到后端验证
addLog
(
'发送凭证到后端验证...'
);
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
1500
));
// 模拟后端验证成功
user
.
value
=
{
id
:
'1234567890'
,
name
:
'张三'
,
given_name
:
'三'
,
family_name
:
'张'
,
picture
:
'https://randomuser.me/api/portraits/men/32.jpg'
,
email
:
'zhangsan@example.com'
,
email_verified
:
true
,
locale
:
'zh-CN'
};
addLog
(
'✅ 后端验证成功!'
);
addLog
(
`用户信息:
${
JSON
.
stringify
(
user
.
value
,
null
,
2
)}
`
);
addLog
(
'登录成功!即将跳转到仪表盘...'
);
}
catch
(
error
)
{
addLog
(
`❌ 登录失败:
${
error
.
message
}
`
);
}
};
// 模拟成功登录
const
simulateSuccess
=
()
=>
{
addLog
(
'模拟成功登录...'
);
user
.
value
=
{
id
:
'1234567890'
,
name
:
'李四'
,
given_name
:
'四'
,
family_name
:
'李'
,
picture
:
'https://randomuser.me/api/portraits/women/44.jpg'
,
email
:
'lisi@example.com'
,
email_verified
:
true
,
locale
:
'zh-CN'
};
addLog
(
'✅ 模拟登录成功!'
);
};
// 重置演示
const
resetDemo
=
()
=>
{
statusLog
.
value
=
'演示已重置...
\
n'
;
user
.
value
=
null
;
showError
.
value
=
false
;
loading
.
value
=
false
;
addLog
(
'等待用户操作...'
);
};
// 组件挂载时检查 SDK
onMounted
(()
=>
{
addLog
(
'组件已挂载,检查 Google SDK 状态...'
);
checkGoogleSDK
();
});
</
script
>
<
style
>
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
font-family
:
'Segoe UI'
,
Tahoma
,
Geneva
,
Verdana
,
sans-serif
;
}
body
{
background
:
linear-gradient
(
135deg
,
#1a2a6c
,
#b21f1f
,
#1a2a6c
);
min-height
:
100vh
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
padding
:
20px
;
color
:
#333
;
}
.container
{
max-width
:
1200px
;
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
}
header
{
text-align
:
center
;
margin-bottom
:
40px
;
color
:
white
;
text-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.3
);
}
header
h1
{
font-size
:
2.8rem
;
margin-bottom
:
10px
;
background
:
linear-gradient
(
to
right
,
#4285f4
,
#34a853
,
#fbbc05
,
#ea4335
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
}
header
p
{
font-size
:
1.2rem
;
max-width
:
800px
;
margin
:
0
auto
;
opacity
:
0.9
;
}
.card
{
background
:
rgba
(
255
,
255
,
255
,
0.95
);
border-radius
:
16px
;
box-shadow
:
0
10px
30px
rgba
(
0
,
0
,
0
,
0.2
);
overflow
:
hidden
;
width
:
100%
;
max-width
:
1000px
;
display
:
flex
;
flex-direction
:
column
;
}
.card-header
{
background
:
linear-gradient
(
90deg
,
#4285f4
,
#34a853
);
color
:
white
;
padding
:
25px
30px
;
text-align
:
center
;
}
.card-body
{
padding
:
30px
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
30px
;
}
.login-section
{
flex
:
1
;
min-width
:
300px
;
}
.debug-section
{
flex
:
1
;
min-width
:
300px
;
background
:
#f8f9fa
;
border-radius
:
12px
;
padding
:
25px
;
}
.debug-section
h3
{
color
:
#ea4335
;
margin-bottom
:
15px
;
font-size
:
1.4rem
;
}
.google-btn-container
{
display
:
flex
;
justify-content
:
center
;
margin
:
30px
0
;
position
:
relative
;
}
.google-btn
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background
:
white
;
color
:
#444
;
border
:
1px
solid
#ddd
;
border-radius
:
4px
;
padding
:
12px
20px
;
font-size
:
16px
;
font-weight
:
500
;
cursor
:
pointer
;
transition
:
all
0.3s
ease
;
box-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.1
);
width
:
100%
;
max-width
:
280px
;
}
.google-btn
:hover
{
background
:
#f8f9fa
;
box-shadow
:
0
4px
8px
rgba
(
0
,
0
,
0
,
0.15
);
}
.google-btn
i
{
font-size
:
20px
;
margin-right
:
12px
;
color
:
#4285f4
;
}
.divider
{
display
:
flex
;
align-items
:
center
;
margin
:
25px
0
;
}
.divider-line
{
flex
:
1
;
height
:
1px
;
background
:
#eee
;
}
.divider-text
{
padding
:
0
15px
;
color
:
#777
;
font-size
:
14px
;
}
.status-panel
{
margin-top
:
30px
;
padding
:
20px
;
border-radius
:
8px
;
background
:
#f8f9fa
;
border-left
:
4px
solid
#4285f4
;
}
.status-panel
h3
{
margin-bottom
:
15px
;
color
:
#4285f4
;
}
.status-content
{
font-family
:
monospace
;
background
:
#2d2d2d
;
color
:
#f8f8f2
;
padding
:
15px
;
border-radius
:
6px
;
max-height
:
200px
;
overflow-y
:
auto
;
font-size
:
14px
;
white-space
:
pre-wrap
;
}
.user-info
{
display
:
flex
;
align-items
:
center
;
margin-top
:
20px
;
padding
:
15px
;
background
:
#e8f0fe
;
border-radius
:
8px
;
}
.avatar
{
width
:
60px
;
height
:
60px
;
border-radius
:
50%
;
margin-right
:
15px
;
object-fit
:
cover
;
border
:
2px
solid
#4285f4
;
}
.user-details
h4
{
font-size
:
18px
;
margin-bottom
:
5px
;
color
:
#202124
;
}
.user-details
p
{
color
:
#5f6368
;
font-size
:
14px
;
}
.solution-list
{
margin-top
:
20px
;
}
.solution-item
{
padding
:
12px
15px
;
margin-bottom
:
10px
;
background
:
white
;
border-radius
:
8px
;
box-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.05
);
display
:
flex
;
align-items
:
flex-start
;
}
.solution-icon
{
margin-right
:
12px
;
font-size
:
18px
;
min-width
:
24px
;
color
:
#34a853
;
}
.solution-content
h4
{
margin-bottom
:
5px
;
color
:
#202124
;
}
.solution-content
p
{
color
:
#5f6368
;
font-size
:
14px
;
line-height
:
1.5
;
}
.btn
{
display
:
inline-block
;
padding
:
12px
25px
;
background
:
linear-gradient
(
90deg
,
#4285f4
,
#34a853
);
color
:
white
;
border
:
none
;
border-radius
:
6px
;
font-size
:
16px
;
font-weight
:
600
;
cursor
:
pointer
;
transition
:
all
0.3s
;
margin-top
:
15px
;
text-decoration
:
none
;
}
.btn
:hover
{
transform
:
translateY
(
-2px
);
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.15
);
}
.btn-outline
{
background
:
transparent
;
border
:
2px
solid
#4285f4
;
color
:
#4285f4
;
margin-left
:
10px
;
}
.loading-spinner
{
display
:
inline-block
;
width
:
20px
;
height
:
20px
;
border
:
3px
solid
rgba
(
255
,
255
,
255
,
.3
);
border-radius
:
50%
;
border-top-color
:
white
;
animation
:
spin
1s
ease-in-out
infinite
;
margin-right
:
10px
;
}
@keyframes
spin
{
to
{
transform
:
rotate
(
360deg
);
}
}
.error-message
{
color
:
#ea4335
;
background
:
#fce8e6
;
padding
:
15px
;
border-radius
:
8px
;
margin
:
20px
0
;
display
:
flex
;
align-items
:
center
;
}
.error-icon
{
font-size
:
24px
;
margin-right
:
10px
;
}
footer
{
margin-top
:
40px
;
text-align
:
center
;
color
:
white
;
font-size
:
14px
;
opacity
:
0.8
;
}
@media
(
max-width
:
768px
)
{
.card-body
{
flex-direction
:
column
;
}
header
h1
{
font-size
:
2.2rem
;
}
.btn
{
display
:
block
;
width
:
100%
;
margin
:
10px
0
;
}
.btn-outline
{
margin-left
:
0
;
}
}
</
style
>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment