feat(web): #558 微信登录体验优化 #676

Merged
auto-approve-bot merged 1 commits from feat/558-wechat-login-polish into develop 2026-07-21 11:59:06 +08:00
3 changed files with 67 additions and 21 deletions
+41 -9
View File
@@ -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()
+7
View File
@@ -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) {
+19 -12
View File
@@ -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 = () => {