From 0e6bc6d7835ee9307f444e5ab6c192b1b38f1432 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 16:21:15 +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?=E6=9B=BF=E6=8D=A2=E5=BE=AE=E4=BF=A1=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/pages/login/login.module.scss | 47 +- nkebao/src/pages/login/login.tsx | 724 +++++++++++------------ nkebao/src/styles/global.scss | 11 + 3 files changed, 391 insertions(+), 391 deletions(-) diff --git a/nkebao/src/pages/login/login.module.scss b/nkebao/src/pages/login/login.module.scss index 0f9408ea..45527883 100644 --- a/nkebao/src/pages/login/login.module.scss +++ b/nkebao/src/pages/login/login.module.scss @@ -1,6 +1,6 @@ .login-page { min-height: 100vh; - background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + background: var(--primary-gradient); display: flex; align-items: center; justify-content: center; @@ -63,7 +63,7 @@ .login-container { width: 100%; max-width: 420px; - background: rgba(255, 255, 255, 0.95); + background: #ffffff; backdrop-filter: blur(20px); border-radius: 24px; padding: 32px 24px; @@ -89,20 +89,20 @@ .logo-icon { width: 40px; height: 40px; - background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + background: var(--primary-gradient); 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); + box-shadow: 0 6px 12px var(--primary-shadow); } .app-name { font-size: 24px; font-weight: 800; - background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -150,26 +150,15 @@ z-index: 2; &.active { - color: #1890ff; + color: var(--primary-color); font-weight: 600; + background: white; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } } .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%); - } + display: none; // 隐藏分割线指示器 } // 表单样式 @@ -201,9 +190,9 @@ transition: all 0.3s ease; &:focus-within { - border-color: #1890ff; + border-color: var(--primary-color); background: white; - box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1); + box-shadow: 0 0 0 3px var(--primary-shadow-light); } } @@ -241,14 +230,14 @@ transition: color 0.3s ease; &:hover { - color: #1890ff; + color: var(--primary-color); } } .send-code-btn { padding: 6px 12px; margin-right: 6px; - background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + background: var(--primary-gradient); color: white; border: none; border-radius: 6px; @@ -259,7 +248,7 @@ &:hover:not(.disabled) { transform: translateY(-1px); - box-shadow: 0 3px 8px rgba(24, 144, 255, 0.3); + box-shadow: 0 3px 8px var(--primary-shadow); } &.disabled { @@ -305,7 +294,7 @@ } .agreement-link { - color: #1890ff; + color: var(--primary-color); cursor: pointer; text-decoration: none; white-space: nowrap; @@ -320,14 +309,14 @@ font-size: 15px; font-weight: 600; border-radius: 10px; - background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%); + background: var(--primary-gradient); border: none; - box-shadow: 0 6px 12px rgba(24, 144, 255, 0.3); + box-shadow: 0 6px 12px var(--primary-shadow); transition: all 0.3s ease; &:hover:not(:disabled) { transform: translateY(-1px); - box-shadow: 0 8px 16px rgba(24, 144, 255, 0.4); + box-shadow: 0 8px 16px var(--primary-shadow-dark); } &:disabled { diff --git a/nkebao/src/pages/login/login.tsx b/nkebao/src/pages/login/login.tsx index 52720f7c..25beeead 100644 --- a/nkebao/src/pages/login/login.tsx +++ b/nkebao/src/pages/login/login.tsx @@ -1,362 +1,362 @@ -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 [countdown, setCountdown] = useState(0); - const [showPassword, setShowPassword] = useState(false); - const [agreeToTerms, setAgreeToTerms] = useState(false); - - 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 { - 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")} - > - 验证码登录 -
-
-
- -
- {/* 手机号输入 */} -
- -
- +86 - -
-
- - {/* 密码输入 */} - {activeTab === "password" && ( -
- -
- -
setShowPassword(!showPassword)} - > - {showPassword ? : } -
-
-
- )} - - {/* 验证码输入 */} - {activeTab === "verification" && ( -
- -
- - -
-
- )} - - {/* 用户协议 */} -
- - - 我已阅读并同意 - - 《存客宝用户协议》 - - 和 - 《隐私政策》 - - -
- - {/* 登录按钮 */} - -
- - {/* 分割线 */} -
- 其他登录方式 -
- - {/* 第三方登录 */} -
-
-
- - - - -
- 微信 -
-
-
- - - -
- Apple -
-
-
-
-
- ); -}; - -export default Login; +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 [countdown, setCountdown] = useState(0); + const [showPassword, setShowPassword] = useState(false); + const [agreeToTerms, setAgreeToTerms] = useState(false); + + 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 { + 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")} + > + 验证码登录 +
+
+
+ +
+ {/* 手机号输入 */} +
+ +
+ +86 + +
+
+ + {/* 密码输入 */} + {activeTab === "password" && ( +
+ +
+ +
setShowPassword(!showPassword)} + > + {showPassword ? : } +
+
+
+ )} + + {/* 验证码输入 */} + {activeTab === "verification" && ( +
+ +
+ + +
+
+ )} + + {/* 用户协议 */} +
+ + + 我已阅读并同意 + + 《存客宝用户协议》 + + 和 + 《隐私政策》 + + +
+ + {/* 登录按钮 */} + +
+ + {/* 分割线 */} +
+ 其他登录方式 +
+ + {/* 第三方登录 */} +
+
+
+ + + + +
+ 微信 +
+
+
+ + + +
+ Apple +
+
+
+
+
+ ); +}; + +export default Login; diff --git a/nkebao/src/styles/global.scss b/nkebao/src/styles/global.scss index 314af21a..079b5d0c 100644 --- a/nkebao/src/styles/global.scss +++ b/nkebao/src/styles/global.scss @@ -1,5 +1,16 @@ /* Reset & 基础样式,兼容移动端和PC端,补充 iOS/安卓容差处理 */ +/* 主题色变量定义 */ +:root { + --primary-color: #188eee; + --primary-color-light: #40a9ff; + --primary-color-dark: #096dd9; + --primary-gradient: linear-gradient(135deg, #188eee 0%, #096dd9 100%); + --primary-shadow: rgba(24, 142, 238, 0.3); + --primary-shadow-light: rgba(24, 142, 238, 0.1); + --primary-shadow-dark: rgba(24, 142, 238, 0.4); +} + /* 1. 通用 Reset */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre,