feat(web): #558 微信登录体验优化 (#676)
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 45m57s
CI/CD Pipeline / Unit Tests (push) Failing after 48m40s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 1371h30m1s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1371h30m9s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1371h30m7s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1371h30m10s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Failing after 1372h2m16s
CI/CD Pipeline / Build Production API Image (push) Failing after 1372h2m20s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 45m57s
CI/CD Pipeline / Unit Tests (push) Failing after 48m40s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 1371h30m1s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1371h30m9s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1371h30m7s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1371h30m10s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Failing after 1372h2m16s
CI/CD Pipeline / Build Production API Image (push) Failing after 1372h2m20s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
This commit was merged in pull request #676.
This commit is contained in:
@@ -20,24 +20,56 @@ export const useLogin = () => {
|
||||
const data = await mutation.mutateAsync(credentials)
|
||||
const refreshToken = data.refresh_token ?? null
|
||||
|
||||
// 先保存 token
|
||||
localStorage.setItem("access_token", data.access_token)
|
||||
if (refreshToken) {
|
||||
localStorage.setItem("refresh_token", refreshToken)
|
||||
} else {
|
||||
localStorage.removeItem("refresh_token")
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const user = await authApi.getCurrentUser()
|
||||
setAuth(user, data.access_token, refreshToken)
|
||||
navigate("/")
|
||||
|
||||
// 跳转到登录前页面或首页
|
||||
const redirect = localStorage.getItem("login_redirect") || "/"
|
||||
localStorage.removeItem("login_redirect")
|
||||
navigate(redirect, { replace: true })
|
||||
return data
|
||||
}
|
||||
|
||||
return { ...mutation, mutateAsync: login }
|
||||
}
|
||||
|
||||
// 微信登录 Hook(用于回调后处理登录状态)
|
||||
export const useWechatCallback = () => {
|
||||
const navigate = useNavigate()
|
||||
const setAuth = useAuthStore((state) => state.setAuth)
|
||||
const setUser = useAuthStore((state) => state.setUser)
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: ({ code, state }: { code: string; state: string }) =>
|
||||
authApi.wechatCallback(code, state),
|
||||
})
|
||||
|
||||
const handleCallback = async (code: string, state: string) => {
|
||||
// 校验 state
|
||||
const savedState = localStorage.getItem("wechat_state")
|
||||
if (!savedState || savedState !== state) {
|
||||
throw new Error("安全校验失败")
|
||||
}
|
||||
localStorage.removeItem("wechat_state")
|
||||
|
||||
const result = await mutation.mutateAsync({ code, state })
|
||||
const user = await authApi.getCurrentUser()
|
||||
setAuth(user, result.access_token, result.refresh_token)
|
||||
|
||||
return { ...result, user }
|
||||
}
|
||||
|
||||
// 绑定成功后跳转
|
||||
const finishLogin = () => {
|
||||
const redirect = localStorage.getItem("login_redirect") || "/"
|
||||
localStorage.removeItem("login_redirect")
|
||||
navigate(redirect, { replace: true })
|
||||
}
|
||||
|
||||
return { ...mutation, handleCallback, finishLogin, setUser }
|
||||
}
|
||||
|
||||
// 注册 Hook
|
||||
export const useRegister = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -41,6 +41,13 @@ const Login: React.FC = () => {
|
||||
const result = await getWechatAuthUrl()
|
||||
// 保存 state 到 localStorage 用于回调时验证
|
||||
localStorage.setItem("wechat_state", result.state)
|
||||
// 记录登录前的来源页,登录成功后跳回
|
||||
const from = window.location.pathname + window.location.search
|
||||
if (from !== "/login" && from !== "/register") {
|
||||
localStorage.setItem("login_redirect", from)
|
||||
} else {
|
||||
localStorage.removeItem("login_redirect")
|
||||
}
|
||||
// 跳转到微信授权页
|
||||
window.location.href = result.auth_url
|
||||
} catch (error) {
|
||||
|
||||
@@ -28,15 +28,16 @@ const WechatCallback: React.FC = () => {
|
||||
|
||||
const handleCallback = async () => {
|
||||
try {
|
||||
const result = await wechatCallback(code, state)
|
||||
|
||||
// 保存 token
|
||||
localStorage.setItem("access_token", result.access_token)
|
||||
if (result.refresh_token) {
|
||||
localStorage.setItem("refresh_token", result.refresh_token)
|
||||
} else {
|
||||
localStorage.removeItem("refresh_token")
|
||||
// 校验 state,防止 CSRF
|
||||
const savedState = localStorage.getItem("wechat_state")
|
||||
if (!savedState || savedState !== state) {
|
||||
setError("安全校验失败,请重新登录")
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
localStorage.removeItem("wechat_state")
|
||||
|
||||
const result = await wechatCallback(code, state)
|
||||
|
||||
// 获取用户信息
|
||||
const userData = await getCurrentUser()
|
||||
@@ -44,9 +45,11 @@ const WechatCallback: React.FC = () => {
|
||||
setAuth(user, result.access_token, result.refresh_token)
|
||||
|
||||
if (result.binding_complete) {
|
||||
// 已绑定,直接跳转到首页
|
||||
// 已绑定,跳转到登录前页面或首页
|
||||
message.success("登录成功")
|
||||
navigate("/app/dashboard")
|
||||
const redirect = localStorage.getItem("login_redirect") || "/"
|
||||
localStorage.removeItem("login_redirect")
|
||||
navigate(redirect, { replace: true })
|
||||
} else {
|
||||
// 未绑定,显示绑定弹窗
|
||||
setLoading(false)
|
||||
@@ -61,10 +64,14 @@ const WechatCallback: React.FC = () => {
|
||||
handleCallback()
|
||||
}, [searchParams, navigate, setAuth])
|
||||
|
||||
const handleBindSuccess = (_user: User) => {
|
||||
const handleBindSuccess = (user: User) => {
|
||||
const setUser = useAuthStore.getState().setUser
|
||||
setUser(user)
|
||||
setShowBindModal(false)
|
||||
message.success("绑定成功")
|
||||
navigate("/app/dashboard")
|
||||
const redirect = localStorage.getItem("login_redirect") || "/"
|
||||
localStorage.removeItem("login_redirect")
|
||||
navigate(redirect, { replace: true })
|
||||
}
|
||||
|
||||
const handleBindCancel = () => {
|
||||
|
||||
Reference in New Issue
Block a user