From a336d1173b1640aa21cc5ade976f515a2e66d45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC=E9=87=8C=E7=9A=84=E6=B0=B8?= =?UTF-8?q?=E5=B9=B3?= Date: Fri, 18 Jul 2025 15:56:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B=20?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=A1=B5=E9=9D=A2=E6=96=B0=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/api/auth.ts | 54 +++ nkebao/src/api/request.ts | 23 +- nkebao/src/pages/login/api.ts | 53 +++ nkebao/src/pages/login/login.module.scss | 462 +++++++++++++++++++++++ nkebao/src/pages/login/login.tsx | 396 ++++++++++++++++--- nkebao/src/router/module/home.tsx | 17 - nkebao/src/router/module/index.tsx | 35 ++ nkebao/src/router/permissionRoute.tsx | 44 ++- nkebao/src/store/module/user.ts | 37 +- 9 files changed, 1018 insertions(+), 103 deletions(-) create mode 100644 nkebao/src/api/auth.ts create mode 100644 nkebao/src/pages/login/api.ts delete mode 100644 nkebao/src/router/module/home.tsx create mode 100644 nkebao/src/router/module/index.tsx diff --git a/nkebao/src/api/auth.ts b/nkebao/src/api/auth.ts new file mode 100644 index 00000000..36b1a82e --- /dev/null +++ b/nkebao/src/api/auth.ts @@ -0,0 +1,54 @@ +import request from './request'; + +export interface LoginParams { + phone: string; + password?: string; + verificationCode?: string; +} + +export interface LoginResponse { + code: number; + msg: string; + data: { + token: string; + token_expired: string; + member: { + id: string; + name: string; + phone: string; + s2_accountId: string; + avatar?: string; + email?: string; + }; + }; +} + +export interface SendCodeResponse { + code: number; + msg: string; +} + +// 密码登录 +export function loginWithPassword(phone: string, password: string) { + return request('/v1/auth/login', { phone, password }, 'POST'); +} + +// 验证码登录 +export function loginWithCode(phone: string, verificationCode: string) { + return request('/v1/auth/login-code', { phone, verificationCode }, 'POST'); +} + +// 发送验证码 +export function sendVerificationCode(phone: string) { + return request('/v1/auth/send-code', { phone }, 'POST'); +} + +// 退出登录 +export function logout() { + return request('/v1/auth/logout', {}, 'POST'); +} + +// 获取用户信息 +export function getUserInfo() { + return request('/v1/auth/user-info', {}, 'GET'); +} \ No newline at end of file diff --git a/nkebao/src/api/request.ts b/nkebao/src/api/request.ts index ee008705..2c5372bf 100644 --- a/nkebao/src/api/request.ts +++ b/nkebao/src/api/request.ts @@ -27,33 +27,24 @@ instance.interceptors.response.use( if (code === 200 || success) { return res.data.data ?? res.data; } - // 业务错误统一提示 Toast.show({ content: msg || '接口错误', position: 'top' }); - // 分类处理 if (code === 401) { - // 未登录或登录失效 localStorage.removeItem('token'); - } else if (code === 403) { - // 无权限 - } else if (code === 500) { - // 服务端异常 + const currentPath = window.location.pathname + window.location.search; + if (currentPath === '/login') { + window.location.href = '/login'; + } else { + window.location.href = `/login?redirect=${encodeURIComponent(currentPath)}`; + } } return Promise.reject(msg || '接口错误'); }, err => { - // 网络错误、超时等 Toast.show({ content: err.message || '网络异常', position: 'top' }); return Promise.reject(err); } ); -/** - * @param url 接口地址 - * @param data 请求参数 - * @param method 请求方法 - * @param config axios 配置 - * @param debounceGap 防抖时间(毫秒),不传则用默认值 - */ export function request( url: string, data?: any, @@ -84,4 +75,4 @@ export function request( return instance(axiosConfig); } -export default request; \ No newline at end of file +export default request; diff --git a/nkebao/src/pages/login/api.ts b/nkebao/src/pages/login/api.ts new file mode 100644 index 00000000..1aba513e --- /dev/null +++ b/nkebao/src/pages/login/api.ts @@ -0,0 +1,53 @@ +import request from '@/api/request'; +export interface LoginParams { + phone: string; + password?: string; + verificationCode?: string; +} + +export interface LoginResponse { + code: number; + msg: string; + data: { + token: string; + token_expired: string; + member: { + id: string; + name: string; + phone: string; + s2_accountId: string; + avatar?: string; + email?: string; + }; + }; +} + +export interface SendCodeResponse { + code: number; + msg: string; +} + +// 密码登录 +export function loginWithPassword(params:any) { + return request('/v1/auth/login', params, 'POST'); +} + +// 验证码登录 +export function loginWithCode(params:any) { + return request('/v1/auth/login-code', params, 'POST'); +} + +// 发送验证码 +export function sendVerificationCode(params:any) { + return request('/v1/auth/send-code',params, 'POST'); +} + +// 退出登录 +export function logout() { + return request('/v1/auth/logout', {}, 'POST'); +} + +// 获取用户信息 +export function getUserInfo() { + return request('/v1/auth/user-info', {}, 'GET'); +} \ No newline at end of file diff --git a/nkebao/src/pages/login/login.module.scss b/nkebao/src/pages/login/login.module.scss index e69de29b..0f9408ea 100644 --- a/nkebao/src/pages/login/login.module.scss +++ b/nkebao/src/pages/login/login.module.scss @@ -0,0 +1,462 @@ +.login-page { + min-height: 100vh; + background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + position: relative; + overflow: hidden; +} + +// 背景装饰 +.bg-decoration { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + z-index: 0; +} + +.bg-circle { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; + + &:nth-child(1) { + width: 200px; + height: 200px; + top: -100px; + right: -100px; + animation-delay: 0s; + } + + &:nth-child(2) { + width: 150px; + height: 150px; + bottom: -75px; + left: -75px; + animation-delay: 2s; + } + + &:nth-child(3) { + width: 100px; + height: 100px; + top: 50%; + right: 10%; + animation-delay: 4s; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px) rotate(0deg); + } + 50% { + transform: translateY(-20px) rotate(180deg); + } +} + +.login-container { + width: 100%; + max-width: 420px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 24px; + padding: 32px 24px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + position: relative; + z-index: 1; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.login-header { + text-align: center; + margin-bottom: 24px; +} + +.logo-section { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-bottom: 16px; +} + +.logo-icon { + width: 40px; + height: 40px; + background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + box-shadow: 0 6px 12px rgba(24, 144, 255, 0.3); +} + +.app-name { + font-size: 24px; + font-weight: 800; + background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: 0; +} + +.welcome-text { + font-size: 18px; + font-weight: 600; + color: #333; + margin: 0 0 4px 0; +} + +.subtitle { + font-size: 13px; + color: #666; + margin: 0; +} + +.form-container { + margin-bottom: 20px; +} + +// 标签页样式 +.tab-container { + display: flex; + background: #f8f9fa; + border-radius: 10px; + padding: 3px; + margin-bottom: 24px; + position: relative; +} + +.tab-item { + flex: 1; + text-align: center; + padding: 10px 12px; + font-size: 13px; + font-weight: 500; + color: #666; + cursor: pointer; + border-radius: 7px; + transition: all 0.3s ease; + position: relative; + z-index: 2; + + &.active { + color: #1890ff; + font-weight: 600; + } +} + +.tab-indicator { + position: absolute; + top: 3px; + left: 3px; + width: calc(50% - 3px); + height: calc(100% - 6px); + background: white; + border-radius: 7px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; + z-index: 1; + + &.slide { + transform: translateX(100%); + } +} + +// 表单样式 +.login-form { + :global(.adm-form) { + --adm-font-size-main: 14px; + } +} + +.input-group { + margin-bottom: 18px; +} + +.input-label { + display: block; + font-size: 13px; + font-weight: 600; + color: #333; + margin-bottom: 6px; +} + +.input-wrapper { + position: relative; + display: flex; + align-items: center; + background: #f8f9fa; + border: 2px solid transparent; + border-radius: 10px; + transition: all 0.3s ease; + + &:focus-within { + border-color: #1890ff; + background: white; + box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1); + } +} + +.input-prefix { + padding: 0 12px; + color: #666; + font-size: 13px; + font-weight: 500; + border-right: 1px solid #e5e5e5; +} + +.phone-input, +.password-input, +.code-input { + flex: 1; + border: none !important; + background: transparent !important; + padding: 12px 14px !important; + font-size: 15px !important; + color: #333 !important; + + &::placeholder { + color: #999; + } + + &:focus { + box-shadow: none !important; + } +} + +.eye-icon { + padding: 0 12px; + color: #666; + cursor: pointer; + transition: color 0.3s ease; + + &:hover { + color: #1890ff; + } +} + +.send-code-btn { + padding: 6px 12px; + margin-right: 6px; + background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + color: white; + border: none; + border-radius: 6px; + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + + &:hover:not(.disabled) { + transform: translateY(-1px); + box-shadow: 0 3px 8px rgba(24, 144, 255, 0.3); + } + + &.disabled { + background: #e5e5e5; + color: #999; + cursor: not-allowed; + transform: none; + box-shadow: none; + } +} + +.agreement-section { + margin-bottom: 24px; + padding: 12px; + background: #f8f9fa; + border-radius: 10px; + border: 1px solid #e5e5e5; +} + +.agreement-checkbox { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: #666; + line-height: 1.4; + white-space: nowrap; + + :global(.adm-checkbox) { + margin-top: 0; + flex-shrink: 0; + } +} + +.agreement-text { + flex: 1; + display: flex; + align-items: center; + flex-wrap: nowrap; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.agreement-link { + color: #1890ff; + cursor: pointer; + text-decoration: none; + white-space: nowrap; + + &:hover { + text-decoration: underline; + } +} + +.login-btn { + height: 46px; + font-size: 15px; + font-weight: 600; + border-radius: 10px; + background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + border: none; + box-shadow: 0 6px 12px rgba(24, 144, 255, 0.3); + transition: all 0.3s ease; + + &:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 8px 16px rgba(24, 144, 255, 0.4); + } + + &:disabled { + background: #e5e5e5; + color: #999; + transform: none; + box-shadow: none; + } +} + +.divider { + position: relative; + text-align: center; + margin: 24px 0; + + &::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: #e5e5e5; + } + + span { + background: rgba(255, 255, 255, 0.95); + padding: 0 12px; + color: #999; + font-size: 11px; + font-weight: 500; + } +} + +.third-party-login { + display: flex; + justify-content: center; + gap: 20px; +} + +.third-party-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + cursor: pointer; + padding: 12px; + border-radius: 10px; + transition: all 0.3s ease; + + &:hover { + background: #f8f9fa; + transform: translateY(-1px); + } + + span { + font-size: 11px; + color: #666; + font-weight: 500; + } +} + +.wechat-icon, +.apple-icon { + width: 36px; + height: 36px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 18px; + transition: all 0.3s ease; +} + +.wechat-icon { + background: #07c160; + box-shadow: 0 3px 8px rgba(7, 193, 96, 0.3); + + &:hover { + box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4); + } + + svg { + width: 20px; + height: 20px; + } +} + +.apple-icon { + background: #000; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3); + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + } + + svg { + width: 20px; + height: 20px; + } +} + +// 响应式设计 +@media (max-width: 480px) { + .login-container { + padding: 24px 20px; + margin: 0 12px; + } + + .app-name { + font-size: 22px; + } + + .welcome-text { + font-size: 16px; + } + + .third-party-login { + gap: 16px; + } + + .third-party-item { + padding: 10px; + } + + .wechat-icon, + .apple-icon { + width: 32px; + height: 32px; + } +} diff --git a/nkebao/src/pages/login/login.tsx b/nkebao/src/pages/login/login.tsx index a9a93068..52720f7c 100644 --- a/nkebao/src/pages/login/login.tsx +++ b/nkebao/src/pages/login/login.tsx @@ -1,72 +1,360 @@ -import React, { useState } from "react"; -import { Form, Input, Button, Toast } from "antd-mobile"; -import { EyeInvisibleOutline, EyeOutline } from "antd-mobile-icons"; -import request from "@/api/request"; +import React, { useState, useEffect } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { Form, Input, Button, Toast, Tabs, Checkbox } from "antd-mobile"; +import { + EyeInvisibleOutline, + EyeOutline, + UserOutline, +} from "antd-mobile-icons"; +import { useUserStore } from "@/store/module/user"; +import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api"; import style from "./login.module.scss"; const Login: React.FC = () => { + const [form] = Form.useForm(); + const [activeTab, setActiveTab] = useState("password"); const [loading, setLoading] = useState(false); - const [visible, setVisible] = useState(false); + const [countdown, setCountdown] = useState(0); + const [showPassword, setShowPassword] = useState(false); + const [agreeToTerms, setAgreeToTerms] = useState(false); - const onFinish = async (values: any) => { - setLoading(true); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { login } = useUserStore(); + + // 倒计时效果 + useEffect(() => { + if (countdown > 0) { + const timer = setTimeout(() => setCountdown(countdown - 1), 1000); + return () => clearTimeout(timer); + } + }, [countdown]); + + // 检查URL是否为登录页面 + const isLoginPage = (url: string) => { try { - // 假设接口为 /api/login,实际请根据后端接口调整 - const res = await request("/api/login", values, "POST"); - // 登录成功后保存 token - localStorage.setItem("token", res.token); - Toast.show({ content: "登录成功", position: "top" }); - // 跳转首页或其他页面 - window.location.href = "/"; - } catch (err: any) { - Toast.show({ content: err?.message || "登录失败", position: "top" }); + const urlObj = new URL(url, window.location.origin); + return urlObj.pathname === "/login" || urlObj.pathname.endsWith("/login"); + } catch { + return false; + } + }; + + // 手机号格式验证 + const validatePhone = (phone: string) => { + const phoneRegex = /^1[3-9]\d{9}$/; + return phoneRegex.test(phone); + }; + + // 发送验证码 + const handleSendVerificationCode = async () => { + const phone = form.getFieldValue("phone"); + + if (!phone) { + Toast.show({ content: "请输入手机号", position: "top" }); + return; + } + + if (!validatePhone(phone)) { + Toast.show({ content: "请输入正确的11位手机号", position: "top" }); + return; + } + + try { + setLoading(true); + const response = await sendVerificationCode(phone); + + if (response.code === 200) { + Toast.show({ content: "验证码已发送", position: "top" }); + setCountdown(60); + } else { + Toast.show({ content: response.msg || "发送失败", position: "top" }); + } + } catch (error) { + Toast.show({ content: "发送失败,请稍后重试", position: "top" }); } finally { setLoading(false); } }; + // 表单验证 + const validateForm = async () => { + try { + const values = await form.validateFields(); + + if (!agreeToTerms) { + Toast.show({ content: "请同意用户协议和隐私政策", position: "top" }); + return false; + } + + if (!validatePhone(values.phone)) { + Toast.show({ content: "请输入正确的11位手机号", position: "top" }); + return false; + } + + if (activeTab === "password" && !values.password) { + Toast.show({ content: "请输入密码", position: "top" }); + return false; + } + + if (activeTab === "verification" && !values.verificationCode) { + Toast.show({ content: "请输入验证码", position: "top" }); + return false; + } + + return values; + } catch (error) { + return false; + } + }; + + // 登录处理 + const handleLogin = async () => { + const values = await validateForm(); + if (!values) return; + + setLoading(true); + try { + let response; + + if (activeTab === "password") { + response = await loginWithPassword(values.phone, values.password); + } else { + response = await loginWithCode(values.phone, values.verificationCode); + } + + if (response.code === 200 && response.data) { + // 保存登录信息到localStorage + localStorage.setItem("token", response.data.token); + localStorage.setItem("token_expired", response.data.token_expired); + localStorage.setItem("s2_accountId", response.data.member.s2_accountId); + localStorage.setItem("userInfo", JSON.stringify(response.data.member)); + + // 更新状态管理 + login(response.data.token, response.data.member); + + Toast.show({ content: "登录成功", position: "top" }); + + // 跳转到首页或重定向URL + const returnUrl = searchParams.get("returnUrl"); + if (returnUrl) { + const decodedUrl = decodeURIComponent(returnUrl); + if (isLoginPage(decodedUrl)) { + navigate("/"); + } else { + window.location.href = decodedUrl; + } + } else { + navigate("/"); + } + } else { + Toast.show({ content: response.msg || "登录失败", position: "top" }); + } + } catch (error: any) { + Toast.show({ + content: error?.message || "登录失败,请稍后重试", + position: "top", + }); + } finally { + setLoading(false); + } + }; + + // 第三方登录处理 + const handleWechatLogin = () => { + Toast.show({ content: "微信登录功能开发中", position: "top" }); + }; + + const handleAppleLogin = () => { + Toast.show({ content: "Apple登录功能开发中", position: "top" }); + }; + return (
-
账号登录
-
+
+
+
+
+ +
+ {/* Logo和标题区域 */} +
+
+
+ +
+

存客宝

+
+

欢迎回来

+

登录您的账户继续使用

+
+ + {/* 登录表单 */} +
+ {/* 标签页切换 */} +
+
setActiveTab("password")} + > + 密码登录 +
+
setActiveTab("verification")} + > + 验证码登录 +
+
+
+ + - 登录 - - } - > - - - - - setVisible((v) => !v)}> - {visible ? : } + {/* 手机号输入 */} +
+ +
+ +86 +
- } - /> - - +
+ + {/* 密码输入 */} + {activeTab === "password" && ( +
+ +
+ +
setShowPassword(!showPassword)} + > + {showPassword ? : } +
+
+
+ )} + + {/* 验证码输入 */} + {activeTab === "verification" && ( +
+ +
+ + +
+
+ )} + + {/* 用户协议 */} +
+ + + 我已阅读并同意 + + 《存客宝用户协议》 + + 和 + 《隐私政策》 + + +
+ + {/* 登录按钮 */} + + + + {/* 分割线 */} +
+ 其他登录方式 +
+ + {/* 第三方登录 */} +
+
+
+ + + + +
+ 微信 +
+
+
+ + + +
+ Apple +
+
+
+
); }; diff --git a/nkebao/src/router/module/home.tsx b/nkebao/src/router/module/home.tsx deleted file mode 100644 index 17a08aa0..00000000 --- a/nkebao/src/router/module/home.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import Home from "@/pages/home/index"; -import Login from "@/pages/login/login"; - -const homeRoutes = [ - { - path: "/", - element: , - auth: false, // 不需要权限 - }, - { - path: "/login", - element: , - auth: false, // 不需要权限 - }, -]; - -export default homeRoutes; diff --git a/nkebao/src/router/module/index.tsx b/nkebao/src/router/module/index.tsx new file mode 100644 index 00000000..f92633a8 --- /dev/null +++ b/nkebao/src/router/module/index.tsx @@ -0,0 +1,35 @@ +import Home from "@/pages/home/index"; +import Login from "@/pages/login/login"; +import Scene from "@/pages/scene/index"; +import Work from "@/pages/work/index"; +import Mine from "@/pages/mine/index"; + +const routes = [ + { + path: "/", + element: , + auth: true, // 需要登录 + }, + { + path: "/login", + element: , + auth: false, // 不需要权限 + }, + { + path: "/scene", + element: , + auth: true, + }, + { + path: "/work", + element: , + auth: true, + }, + { + path: "/mine", + element: , + auth: true, + }, +]; + +export default routes; diff --git a/nkebao/src/router/permissionRoute.tsx b/nkebao/src/router/permissionRoute.tsx index 66b0b3dc..95d8b89d 100644 --- a/nkebao/src/router/permissionRoute.tsx +++ b/nkebao/src/router/permissionRoute.tsx @@ -1,27 +1,45 @@ -import React from "react"; -import { Navigate, useLocation } from "react-router-dom"; -import { useUserStore } from "@/store/module/user"; // 假设你用 zustand 管理用户状态 +import React, { useEffect } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { useUserStore } from "@/store/module/user"; -interface Props { +interface PermissionRouteProps { children: React.ReactNode; - requiredRole?: string; // 可选:需要的角色 + requiredRole?: string; } -const PermissionRoute: React.FC = ({ children, requiredRole }) => { - const user = useUserStore((state) => state.user); +const PermissionRoute: React.FC = ({ + children, + requiredRole, +}) => { + const { user, isLoggedIn } = useUserStore(); + const navigate = useNavigate(); const location = useLocation(); - // 未登录 - if (!user) { - return ; + useEffect(() => { + // 检查是否已登录 + if (!isLoggedIn || !user) { + const currentPath = location.pathname + location.search; + navigate(`/login?returnUrl=${encodeURIComponent(currentPath)}`); + return; + } + + // 检查角色权限(如果需要) + if (requiredRole && user.role !== requiredRole) { + navigate("/"); + return; + } + }, [isLoggedIn, user, requiredRole, navigate, location]); + + // 如果未登录,不渲染子组件 + if (!isLoggedIn || !user) { + return null; } - // 有角色要求但不满足 + // 如果角色不匹配,不渲染子组件 if (requiredRole && user.role !== requiredRole) { - return ; + return null; } - // 通过 return <>{children}; }; diff --git a/nkebao/src/store/module/user.ts b/nkebao/src/store/module/user.ts index 876c8a77..1df11894 100644 --- a/nkebao/src/store/module/user.ts +++ b/nkebao/src/store/module/user.ts @@ -1,23 +1,54 @@ import { createPersistStore } from '@/store/createPersistStore'; export interface User { + id?: string; name: string; + phone: string; role: string; token: string; + token_expired?: string; + s2_accountId?: string; + avatar?: string; + email?: string; } interface UserState { user: User | null; + isLoggedIn: boolean; setUser: (user: User) => void; clearUser: () => void; + login: (token: string, userInfo: any) => void; + logout: () => void; } export const useUserStore = createPersistStore( (set) => ({ user: null, - setUser: (user) => set({ user }), - clearUser: () => set({ user: null }), + isLoggedIn: false, + setUser: (user) => set({ user, isLoggedIn: true }), + clearUser: () => set({ user: null, isLoggedIn: false }), + login: (token, userInfo) => { + const user: User = { + id: userInfo.id, + name: userInfo.name || userInfo.nickname || userInfo.username, + phone: userInfo.phone, + role: userInfo.role || 'user', + token, + token_expired: userInfo.token_expired, + s2_accountId: userInfo.s2_accountId, + avatar: userInfo.avatar, + email: userInfo.email, + }; + set({ user, isLoggedIn: true }); + }, + logout: () => { + localStorage.removeItem('token'); + localStorage.removeItem('token_expired'); + localStorage.removeItem('s2_accountId'); + localStorage.removeItem('userInfo'); + set({ user: null, isLoggedIn: false }); + }, }), 'user-store', - (state) => ({ user: state.user }) + (state) => ({ user: state.user, isLoggedIn: state.isLoggedIn }) ); \ No newline at end of file