合并login
Change-Id: Ie06ed019cbb00d52e0b9e1f3c7a56c947b57a42c
diff --git a/Merge/front/src/pages/LoginPage/LoginPage.css b/Merge/front/src/pages/LoginPage/LoginPage.css
new file mode 100644
index 0000000..ab1e24c
--- /dev/null
+++ b/Merge/front/src/pages/LoginPage/LoginPage.css
@@ -0,0 +1,1288 @@
+/* 登录页面容器 */
+.login-container {
+ min-height: 100vh;
+ min-height: 100dvh; /* 动态视口高度,避免移动端地址栏影响 */
+ height: 100vh;
+ height: 100dvh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ /* 确保容器稳定定位 */
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ /* 重置文本对齐 */
+ text-align: initial;
+}
+
+/* 小红书风格背景 */
+.login-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: #f8f8f8;
+ z-index: -1;
+}
+
+/* 登录内容区域 */
+.login-content {
+ width: 100%;
+ max-width: 500px; /* 增加桌面端最大宽度 */
+ padding: 0;
+ z-index: 1;
+ /* 确保内容稳定定位 */
+ box-sizing: border-box;
+ position: relative;
+ display: flex;
+ justify-content: center;
+}
+
+/* 小红书风格登录卡片 */
+.login-card {
+ background: #fff;
+ border-radius: 8px;
+ padding: 40px; /* 增加桌面端内边距 */
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+ border: 1px solid #e1e1e1;
+ width: 100%;
+ max-width: 450px; /* 增加桌面端卡片最大宽度 */
+ transition: none;
+}
+
+/* 登录头部 */
+.login-header {
+ text-align: center;
+ margin-bottom: 40px;
+}
+
+/* Logo样式 */
+.logo-section {
+ margin-bottom: 24px;
+}
+
+.logo-icon {
+ width: 60px;
+ height: 60px;
+ background: #ff2442;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 28px;
+ margin: 0 auto 16px;
+ box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+.login-title {
+ font-size: 24px;
+ font-weight: 600;
+ color: #333;
+ margin: 0 0 12px 0;
+ text-align: center;
+}
+
+.login-title::after {
+ display: none;
+}
+
+.login-subtitle {
+ font-size: 14px;
+ color: #999;
+ margin: 0 0 32px 0;
+ font-weight: 400;
+ text-align: center;
+}
+
+/* 表单样式 */
+.login-form {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ width: 100%;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ width: 100%;
+ box-sizing: border-box;
+ margin-bottom: 2px;
+ position: relative; /* 为绝对定位的错误提示提供参考点 */
+}
+
+.form-group .input-wrapper {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.form-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 8px;
+}
+
+.input-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.form-input {
+ width: 100% !important;
+ height: 44px;
+ padding: 12px 16px 12px 48px;
+ border: 1px solid #e1e1e1;
+ border-radius: 6px;
+ font-size: 14px;
+ transition: border-color 0.2s ease;
+ background: #fff;
+ color: #333;
+ box-sizing: border-box !important;
+ flex: 1;
+ min-width: 0;
+}
+
+/* 针对 Antd Input 组件的特定样式 */
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+ width: 100% !important;
+ height: 44px !important;
+ border: 1px solid #e1e1e1 !important;
+ border-radius: 6px !important;
+ padding: 12px 48px 12px 48px !important;
+ font-size: 14px !important;
+ background: #fff !important;
+ color: #333 !important;
+ box-sizing: border-box !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ display: flex !important;
+ align-items: center !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input {
+ width: 100% !important;
+ height: 100% !important;
+ padding: 0 !important;
+ border: none !important;
+ background: transparent !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ box-sizing: border-box !important;
+}
+
+.form-input:focus {
+ outline: none;
+ border-color: #ff2442;
+ box-shadow: none;
+ transform: none;
+}
+
+/* Antd Input focus 样式 */
+.form-input.ant-input:focus,
+.form-input.ant-input-affix-wrapper:focus,
+.form-input.ant-input-affix-wrapper-focused {
+ outline: none !important;
+ border-color: #ff2442 !important;
+ box-shadow: none !important;
+ transform: none !important;
+}
+
+.form-input::placeholder {
+ color: #9ca3af;
+}
+
+.input-icon {
+ position: absolute;
+ left: 16px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #9ca3af;
+ pointer-events: none;
+ transition: color 0.3s ease;
+ z-index: 2;
+}
+
+.form-input:focus + .input-icon {
+ color: #ff2442;
+}
+
+.password-toggle {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ color: #9ca3af;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 2;
+ width: 24px;
+ height: 24px;
+}
+
+.password-toggle:hover {
+ color: #ff2442;
+ background-color: rgba(255, 36, 66, 0.1);
+}
+
+/* 表单选项 */
+.form-options {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 8px;
+ margin-bottom: 16px;
+ width: 100%;
+ flex-wrap: nowrap; /* 确保不换行 */
+ min-height: 24px;
+ gap: 8px; /* 添加基础间距 */
+}
+
+/* Ant Design Checkbox 样式兼容 */
+.form-options .ant-checkbox-wrapper {
+ flex: 0 0 auto; /* 不伸缩,保持原始大小 */
+ font-size: 14px;
+ color: #64748b;
+ white-space: nowrap; /* 防止文字换行 */
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 50%; /* 限制最大宽度 */
+}
+
+.form-options .ant-checkbox-wrapper .ant-checkbox {
+ margin-right: 8px;
+}
+
+.form-options .forgot-password {
+ flex: 0 0 auto; /* 不伸缩,保持原始大小 */
+ margin-left: auto;
+ white-space: nowrap;
+ color: #ff2442;
+ text-decoration: none;
+ font-size: 14px;
+ transition: color 0.3s ease;
+ max-width: 45%; /* 限制最大宽度 */
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.form-options .forgot-password:hover {
+ color: #ff1a3a;
+ text-decoration: underline;
+}
+
+.checkbox-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ font-size: 14px;
+ color: #64748b;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ flex-shrink: 0;
+}
+
+.checkbox {
+ position: relative;
+ width: 18px;
+ height: 18px;
+ margin: 0;
+ cursor: pointer;
+ opacity: 0;
+}
+
+.checkmark {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 18px;
+ height: 18px;
+ background-color: #fff;
+ border: 1px solid #e1e1e1;
+ border-radius: 3px;
+ transition: all 0.2s ease;
+}
+
+.checkbox:checked + .checkmark {
+ background-color: #ff2442;
+ border-color: #ff2442;
+}
+
+.checkmark:after {
+ content: "";
+ position: absolute;
+ display: none;
+ left: 5px;
+ top: 2px;
+ width: 4px;
+ height: 8px;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+}
+
+.checkbox:checked + .checkmark:after {
+ display: block;
+}
+
+.forgot-password {
+ font-size: 14px;
+ color: #ff2442;
+ text-decoration: none;
+ font-weight: 400;
+ transition: color 0.2s ease;
+ margin-left: auto;
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+
+.forgot-password:hover {
+ color: #d91e3a;
+ text-decoration: underline;
+}
+
+/* 小红书风格登录按钮 */
+.login-button {
+ width: 100%;
+ height: 48px; /* 固定高度,防止布局变化 */
+ padding: 12px;
+ background: #ff2442;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 8px;
+ box-sizing: border-box; /* 确保padding包含在总尺寸内 */
+ min-width: 0; /* 防止flex子元素造成宽度变化 */
+}
+
+.login-button:hover:not(:disabled) {
+ background: #d91e3a;
+ transform: none;
+ box-shadow: none;
+}
+
+.login-button:active:not(:disabled) {
+ transform: none;
+}
+
+.login-button:disabled {
+ background: #ccc;
+ cursor: not-allowed;
+ opacity: 0.8;
+}
+
+.login-button.loading {
+ background: #ff7b8a;
+ cursor: not-allowed;
+}
+
+.loading-spinner {
+ width: 16px;
+ height: 16px;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ border-radius: 50%;
+ border-top-color: #fff;
+ animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* 加载遮罩层 */
+.loading-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ backdrop-filter: blur(4px);
+}
+
+.loading-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ background: white;
+ padding: 32px;
+ border-radius: 12px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.loading-spinner-large {
+ width: 40px;
+ height: 40px;
+ border: 4px solid rgba(255, 36, 66, 0.2);
+ border-radius: 50%;
+ border-top-color: #ff2442;
+ animation: spin 1s ease-in-out infinite;
+}
+
+.loading-text {
+ margin: 0;
+ color: #333;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+/* 分隔线 */
+.login-divider {
+ position: relative;
+ text-align: center;
+ margin: 32px 0;
+ color: #9ca3af;
+ font-size: 14px;
+}
+
+.login-divider::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(to right, transparent, #e5e7eb, transparent);
+}
+
+.login-divider span {
+ background: rgba(255, 255, 255, 0.95);
+ padding: 0 16px;
+ position: relative;
+ z-index: 1;
+}
+
+/* 社交登录 */
+.social-login {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.social-button {
+ width: 100%;
+ padding: 12px 16px;
+ border: 1px solid #e1e1e1;
+ border-radius: 6px;
+ background: white;
+ color: #333;
+ font-size: 14px;
+ font-weight: 400;
+ cursor: pointer;
+ transition: border-color 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+}
+
+.social-button:hover {
+ border-color: #ccc;
+}
+
+.social-button.google:hover {
+ border-color: #4285f4;
+ box-shadow: 0 4px 12px rgba(66, 133, 244, 0.2);
+}
+
+.social-button.github:hover {
+ border-color: #333;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+.social-button.xiaohongshu:hover {
+ border-color: #ff2442;
+ box-shadow: 0 4px 12px rgba(255, 36, 66, 0.2);
+}
+
+/* 注册链接 */
+.signup-link {
+ text-align: center;
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px solid #e5e7eb;
+}
+
+.signup-link p {
+ margin: 0;
+ font-size: 14px;
+ color: #64748b;
+}
+
+.signup-link a {
+ color: #ff2442;
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.2s ease;
+}
+
+.signup-link a:hover {
+ color: #d91e3a;
+ text-decoration: underline;
+}
+
+/* 有左侧图标时的内边距调整 */
+.input-wrapper.has-icon .form-input {
+ padding-left: 48px !important;
+}
+
+.input-wrapper.has-icon .form-input.ant-input,
+.input-wrapper.has-icon .form-input.ant-input-affix-wrapper {
+ padding-left: 48px !important;
+}
+
+/* 有右侧切换按钮时的内边距调整 */
+.input-wrapper.has-toggle .form-input {
+ padding-right: 48px !important;
+}
+
+.input-wrapper.has-toggle .form-input.ant-input,
+.input-wrapper.has-toggle .form-input.ant-input-affix-wrapper {
+ padding-right: 48px !important;
+}
+
+/* 没有图标时的内边距调整 */
+.input-wrapper:not(.has-icon) .form-input {
+ padding-left: 16px !important;
+}
+
+.input-wrapper:not(.has-icon) .form-input.ant-input,
+.input-wrapper:not(.has-icon) .form-input.ant-input-affix-wrapper {
+ padding-left: 16px !important;
+}
+
+/* 没有切换按钮时的内边距调整 */
+.input-wrapper:not(.has-toggle) .form-input {
+ padding-right: 16px !important;
+}
+
+.input-wrapper:not(.has-toggle) .form-input.ant-input,
+.input-wrapper:not(.has-toggle) .form-input.ant-input-affix-wrapper {
+ padding-right: 16px !important;
+}
+
+/* 确保输入框内容完全填充 */
+.form-input.ant-input-affix-wrapper .ant-input-suffix {
+ position: absolute !important;
+ right: 12px !important;
+ top: 50% !important;
+ transform: translateY(-50%) !important;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.form-input.ant-input-affix-wrapper .ant-input-prefix {
+ position: absolute !important;
+ left: 16px !important;
+ top: 50% !important;
+ transform: translateY(-50%) !important;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+/* 确保所有输入框完全填充其容器 */
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.form-group .input-wrapper {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+/* 防止输入框溢出容器 */
+.form-input,
+.form-input.ant-input,
+.form-input.ant-input-affix-wrapper {
+ max-width: 100% !important;
+ overflow: hidden !important;
+}
+
+/* 确保内部输入元素不会超出边界 */
+.form-input.ant-input-affix-wrapper .ant-input {
+ max-width: 100% !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+}
+
+/* 精细间距控制 */
+.login-header + .login-form {
+ margin-top: -4px;
+}
+
+.login-form .form-group:not(:last-child) {
+ margin-bottom: 2px;
+}
+
+.login-form .form-group:last-of-type {
+ margin-bottom: 6px;
+}
+
+.login-button + .signup-link {
+ margin-top: 14px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ /* 重置body和html确保一致性 */
+ html, body {
+ height: 100%;
+ height: 100dvh;
+ margin: 0;
+ padding: 0;
+ overflow-x: hidden;
+ box-sizing: border-box;
+ }
+
+ .login-container {
+ padding: 16px;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ min-height: 100dvh; /* 动态视口高度 */
+ /* 强制重置可能影响定位的样式 */
+ margin: 0;
+ box-sizing: border-box;
+ /* 防止内容溢出影响布局 */
+ overflow-x: hidden;
+ overflow-y: auto;
+ /* 确保flexbox在所有移动设备上表现一致 */
+ -webkit-box-align: center;
+ -webkit-box-pack: center;
+ display: flex !important;
+ position: relative;
+ }
+
+ .login-content {
+ max-width: 100%;
+ padding: 20px;
+ /* 确保内容区域稳定 */
+ margin: 0 auto;
+ box-sizing: border-box;
+ /* 防止宽度计算问题 */
+ width: calc(100% - 40px);
+ max-width: 480px; /* 增加最大宽度 */
+ position: relative;
+ display: flex;
+ justify-content: center;
+ }
+
+ .login-card {
+ padding: 32px 28px; /* 增加内边距 */
+ border-radius: 16px;
+ /* 确保卡片稳定定位 */
+ margin: 0;
+ box-sizing: border-box;
+ width: 100%;
+ max-width: 450px; /* 增加卡片最大宽度 */
+ /* 防止backdrop-filter导致的渲染差异 */
+ will-change: auto;
+ position: relative;
+ /* 防止触摸操作影响布局 */
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ .login-title {
+ font-size: 24px;
+ }
+
+ .form-input {
+ width: 100% !important;
+ height: 44px !important;
+ padding: 12px 48px 12px 48px;
+ font-size: 16px; /* 防止iOS Safari缩放 */
+ box-sizing: border-box !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ }
+
+ .form-input.ant-input,
+ .form-input.ant-input-affix-wrapper {
+ width: 100% !important;
+ height: 44px !important;
+ padding: 12px 48px 12px 48px !important;
+ font-size: 16px !important;
+ box-sizing: border-box !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ }
+
+ .form-input.ant-input-affix-wrapper .ant-input {
+ width: 100% !important;
+ height: 100% !important;
+ padding: 0 !important;
+ border: none !important;
+ background: transparent !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ box-sizing: border-box !important;
+ }
+
+ .social-login {
+ gap: 10px;
+ }
+
+ .social-button {
+ padding: 12px 16px;
+ font-size: 14px;
+ }
+
+ .form-options {
+ display: flex !important;
+ flex-direction: row !important;
+ justify-content: space-between !important;
+ align-items: center !important;
+ gap: 8px !important;
+ width: 100% !important;
+ flex-wrap: nowrap !important;
+ min-height: 22px !important;
+ margin-top: 8px !important;
+ margin-bottom: 16px !important;
+ }
+
+ .checkbox-wrapper {
+ font-size: 13px;
+ flex: 0 0 auto !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 48% !important;
+ }
+
+ .forgot-password {
+ font-size: 13px;
+ margin-left: auto;
+ flex: 0 0 auto !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 48% !important;
+ }
+
+ /* Ant Design Checkbox 的特殊处理 */
+ .form-options .ant-checkbox-wrapper {
+ flex: 0 0 auto !important;
+ font-size: 13px !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 48% !important;
+ }
+}
+
+@media (max-width: 480px) {
+ .login-container {
+ padding: 16px;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ min-height: 100dvh; /* 动态视口高度 */
+ /* 强制重置样式 */
+ margin: 0;
+ box-sizing: border-box;
+ position: relative;
+ /* 确保垂直居中 */
+ display: flex !important;
+ }
+
+ .login-content {
+ /* 更严格的尺寸控制 */
+ width: calc(100vw - 32px);
+ max-width: 420px; /* 增加最大宽度 */
+ padding: 16px;
+ margin: 0 auto;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: center;
+ }
+
+ .login-card {
+ padding: 28px 24px; /* 增加内边距 */
+ border-radius: 12px;
+ /* 确保卡片完全稳定 */
+ margin: 0;
+ box-sizing: border-box;
+ width: 100%;
+ position: relative;
+ /* 防止变换导致的位置偏移 */
+ transform: none !important;
+ /* 防止触摸操作影响布局 */
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ /* 防止点击时的高亮效果影响布局 */
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ .login-title {
+ font-size: 22px;
+ }
+
+ .form-input {
+ width: 100% !important;
+ height: 44px !important;
+ padding: 12px 48px 12px 48px;
+ font-size: 16px; /* 防止iOS Safari缩放 */
+ box-sizing: border-box !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ }
+
+ .form-input.ant-input,
+ .form-input.ant-input-affix-wrapper {
+ width: 100% !important;
+ height: 44px !important;
+ padding: 12px 48px 12px 48px !important;
+ font-size: 16px !important;
+ box-sizing: border-box !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ }
+
+ .form-input.ant-input-affix-wrapper .ant-input {
+ width: 100% !important;
+ height: 100% !important;
+ padding: 0 !important;
+ border: none !important;
+ background: transparent !important;
+ flex: 1 !important;
+ min-width: 0 !important;
+ box-sizing: border-box !important;
+ }
+
+ .social-login {
+ gap: 10px;
+ }
+
+ .social-button {
+ padding: 12px 16px;
+ font-size: 14px;
+ }
+
+ .form-options {
+ display: flex !important;
+ flex-direction: row !important;
+ justify-content: space-between !important;
+ align-items: center !important;
+ gap: 6px !important;
+ width: 100% !important;
+ min-height: 20px !important;
+ flex-wrap: nowrap !important;
+ margin-top: 8px !important;
+ margin-bottom: 16px !important;
+ }
+
+ .checkbox-wrapper {
+ flex: 0 0 auto !important;
+ font-size: 12px !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 45% !important;
+ }
+
+ .forgot-password {
+ flex: 0 0 auto !important;
+ font-size: 12px !important;
+ margin-left: auto !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 45% !important;
+ }
+
+/* 移动端优化 */
+ .background-pattern {
+ display: none;
+ }
+
+ /* 禁用可能影响位置的悬停效果 */
+ .login-card:hover {
+ transform: none !important;
+ }
+}
+
+/* 超小屏幕优化(320px及以下) */
+@media (max-width: 320px) {
+ .login-content {
+ padding: 16px;
+ }
+
+ .login-card {
+ padding: 24px;
+ }
+
+ .form-options {
+ display: flex !important;
+ flex-direction: row !important;
+ justify-content: space-between !important;
+ align-items: center !important;
+ gap: 4px !important;
+ width: 100% !important;
+ flex-wrap: nowrap !important;
+ min-height: 18px !important;
+ margin-top: 6px !important;
+ margin-bottom: 14px !important;
+ }
+
+ .checkbox-wrapper {
+ flex: 0 0 auto !important;
+ font-size: 11px !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 42% !important;
+ line-height: 1.2 !important;
+ }
+
+ .forgot-password {
+ flex: 0 0 auto !important;
+ font-size: 11px !important;
+ margin-left: auto !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 42% !important;
+ line-height: 1.2 !important;
+ }
+
+ /* Ant Design Checkbox 的特殊处理 */
+ .form-options .ant-checkbox-wrapper {
+ flex: 0 0 auto !important;
+ font-size: 11px !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 42% !important;
+ line-height: 1.2 !important;
+ }
+}
+
+/* 极小屏幕优化(280px及以下) */
+@media (max-width: 280px) {
+ .form-options {
+ display: flex !important;
+ flex-direction: row !important;
+ justify-content: space-between !important;
+ align-items: center !important;
+ gap: 2px !important;
+ width: 100% !important;
+ flex-wrap: nowrap !important;
+ min-height: 16px !important;
+ margin-top: 6px !important;
+ margin-bottom: 14px !important;
+ }
+
+ .form-options .ant-checkbox-wrapper {
+ flex: 0 0 auto !important;
+ font-size: 10px !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 40% !important;
+ line-height: 1.1 !important;
+ }
+
+ .form-options .forgot-password {
+ flex: 0 0 auto !important;
+ font-size: 10px !important;
+ margin-left: auto !important;
+ white-space: nowrap !important;
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ max-width: 40% !important;
+ line-height: 1.1 !important;
+ }
+
+ .form-options .ant-checkbox-wrapper .ant-checkbox {
+ margin-right: 2px !important;
+ transform: scale(0.8) !important; /* 进一步缩小checkbox */
+ }
+}
+
+/* 高对比度模式支持 */
+@media (prefers-contrast: high) {
+ .login-card {
+ background: white;
+ border: 2px solid #000;
+ }
+
+ .form-input {
+ border-color: #000;
+ }
+
+ .form-input:focus {
+ border-color: #0066cc;
+ box-shadow: 0 0 0 2px #0066cc;
+ }
+}
+
+/* 减少动画模式 */
+@media (prefers-reduced-motion: reduce) {
+ .background-pattern {
+ animation: none;
+ }
+
+ .login-card,
+ .form-input,
+ .login-button,
+ .social-button {
+ transition: none;
+ }
+}
+
+/* 深色模式支持 */
+@media (prefers-color-scheme: dark) {
+ .login-background {
+ background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
+ }
+
+ .login-card {
+ background: rgba(26, 32, 44, 0.95);
+ border-color: rgba(255, 255, 255, 0.1);
+ }
+
+ .login-title {
+ color: #f7fafc;
+ }
+
+ .login-subtitle {
+ color: #a0aec0;
+ }
+
+ .form-label {
+ color: #e2e8f0;
+ }
+
+ .form-input {
+ background: #2d3748;
+ border-color: #4a5568;
+ color: #f7fafc;
+ }
+
+ .form-input:focus {
+ border-color: #ff2442;
+ }
+
+ .social-button {
+ background: #2d3748;
+ border-color: #4a5568;
+ color: #f7fafc;
+ }
+
+ .signup-link {
+ border-color: #4a5568;
+ }
+
+ .signup-link p {
+ color: #a0aec0;
+ }
+
+ /* 深色模式下的错误提示样式 */
+ .error-message {
+ background: rgba(26, 32, 44, 0.95);
+ color: #ff6b6b;
+ }
+
+ /* 深色模式下的错误弹窗样式 */
+ .error-modal .ant-modal-header {
+ background: #2d3748;
+ border-color: #4a5568;
+ }
+
+ .error-modal .ant-modal-title {
+ color: #f7fafc;
+ }
+
+ .error-modal .ant-modal-body {
+ background: #2d3748;
+ color: #f7fafc;
+ }
+
+ .error-modal .ant-modal-footer {
+ background: #2d3748;
+ }
+
+ .error-modal .ant-modal-content {
+ background: #2d3748;
+ }
+}
+
+/* 错误提示样式 - 使用绝对定位避免影响布局 */
+.error-message {
+ position: absolute;
+ top: 95%;
+ left: 4px;
+ right: 4px;
+ font-size: 12px;
+ color: #ff4d4f;
+ margin-top: 4px;
+ display: flex;
+ align-items: center;
+ min-height: 16px;
+ animation: fadeInDown 0.3s ease-out;
+ font-weight: 400;
+ line-height: 1.2;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(4px);
+ padding: 2px 4px;
+ border-radius: 4px;
+ z-index: 10;
+ pointer-events: none; /* 避免干扰用户交互 */
+}
+
+@keyframes fadeInDown {
+ from {
+ opacity: 0;
+ transform: translateY(-8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* 输入框错误状态样式 */
+.form-input.input-error,
+.form-input.input-error.ant-input,
+.form-input.input-error.ant-input-affix-wrapper {
+ border-color: #ff4d4f !important;
+ box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1) !important;
+ transition: all 0.3s ease !important;
+}
+
+.form-input.input-error:focus,
+.form-input.input-error.ant-input:focus,
+.form-input.input-error.ant-input-affix-wrapper:focus,
+.form-input.input-error.ant-input-affix-wrapper-focused {
+ border-color: #ff4d4f !important;
+ box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
+}
+
+/* 错误状态下的图标颜色 */
+.form-input.input-error .anticon {
+ color: #ff4d4f !important;
+}
+
+/* 确保表单组间距一致 */
+.form-group {
+ margin-bottom: 0px;
+}
+
+.form-group:last-of-type {
+ margin-bottom: 0px;
+}
+
+/* 错误弹窗样式 */
+.error-modal .ant-modal-header {
+ background: #fff;
+ border-bottom: 1px solid #f0f0f0;
+ padding: 16px 24px;
+}
+
+.error-modal .ant-modal-title {
+ color: #333;
+ font-weight: 600;
+ font-size: 16px;
+}
+
+.error-modal .ant-modal-body {
+ padding: 16px 24px 24px;
+}
+
+.error-modal .ant-modal-footer {
+ padding: 12px 24px 24px;
+ border-top: none;
+ text-align: center;
+}
+
+.error-modal .ant-btn-primary {
+ background: #ff2442;
+ border-color: #ff2442;
+ font-weight: 500;
+ height: 40px;
+ padding: 0 24px;
+ border-radius: 6px;
+ transition: all 0.2s ease;
+}
+
+.error-modal .ant-btn-primary:hover {
+ background: #d91e3a;
+ border-color: #d91e3a;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(255, 36, 66, 0.3);
+}
+
+.error-modal .ant-modal-content {
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+}
+
+/* 错误弹窗遮罩层 */
+.error-modal .ant-modal-mask {
+ background: rgba(0, 0, 0, 0.6);
+ backdrop-filter: blur(4px);
+}
+
+/* 错误弹窗动画 */
+.error-modal .ant-modal {
+ animation: errorModalSlideIn 0.3s ease-out;
+}
+
+@keyframes errorModalSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px) scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
diff --git a/Merge/front/src/pages/LoginPage/LoginPage.js b/Merge/front/src/pages/LoginPage/LoginPage.js
new file mode 100644
index 0000000..c315b7d
--- /dev/null
+++ b/Merge/front/src/pages/LoginPage/LoginPage.js
@@ -0,0 +1,380 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { Input, Checkbox, Modal, Alert } from 'antd';
+import { MailOutlined, LockOutlined, ExclamationCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import {
+ getRememberedLoginInfo,
+ saveRememberedLoginInfo,
+ saveAuthInfo,
+ isLoggedIn
+} from '../../utils/auth';
+import { hashPassword } from '../../utils/crypto';
+import './LoginPage.css';
+
+const baseURL = 'http://10.126.59.25:8082';
+
+const LoginPage = () => {
+ const [formData, setFormData] = useState({
+ email: '',
+ password: ''
+ });
+
+ const [rememberMe, setRememberMe] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [errors, setErrors] = useState({
+ email: '',
+ password: ''
+ });
+ const [errorModal, setErrorModal] = useState({
+ visible: false,
+ title: '',
+ content: ''
+ });
+ const [successAlert, setSuccessAlert] = useState({
+ visible: false,
+ message: ''
+ });
+
+ // 显示错误弹窗
+ const showErrorModal = (title, content) => {
+ setErrorModal({
+ visible: true,
+ title: title,
+ content: content
+ });
+ };
+
+ // 关闭错误弹窗
+ const closeErrorModal = () => {
+ setErrorModal({
+ visible: false,
+ title: '',
+ content: ''
+ });
+ };
+
+ // 显示成功提示
+ const showSuccessAlert = (message) => {
+ setSuccessAlert({
+ visible: true,
+ message: message
+ });
+
+ // 3秒后自动隐藏
+ setTimeout(() => {
+ setSuccessAlert({
+ visible: false,
+ message: ''
+ });
+ }, 3000);
+ };
+
+ // 页面加载时检查是否有记住的登录信息
+ useEffect(() => {
+ // 检查是否已经登录
+ if (isLoggedIn()) {
+ // 如果已经有token,可以选择直接跳转到主页面
+ // window.location.href = '/test-dashboard';
+ console.log('用户已登录');
+ }
+
+ // 获取记住的登录信息
+ const rememberedInfo = getRememberedLoginInfo();
+ if (rememberedInfo.rememberMe && rememberedInfo.email) {
+ setFormData({
+ email: rememberedInfo.email,
+ password: rememberedInfo.password
+ });
+ setRememberMe(true);
+ }
+ }, []);
+
+ const handleEmailChange = (e) => {
+ const value = e.target.value;
+ setFormData(prev => ({
+ ...prev,
+ email: value
+ }));
+
+ // 清除邮箱错误提示
+ if (errors.email) {
+ setErrors(prev => ({
+ ...prev,
+ email: ''
+ }));
+ }
+ };
+
+ const handlePasswordChange = (e) => {
+ const value = e.target.value;
+ setFormData(prev => ({
+ ...prev,
+ password: value
+ }));
+
+ // 清除密码错误提示
+ if (errors.password) {
+ setErrors(prev => ({
+ ...prev,
+ password: ''
+ }));
+ }
+ };
+
+ const handleRememberMeChange = (e) => {
+ const checked = e.target.checked;
+ setRememberMe(checked);
+
+ // 如果取消记住我,清除已保存的登录信息
+ if (!checked) {
+ saveRememberedLoginInfo('', '', false);
+ }
+ };
+
+ const validateForm = () => {
+ const newErrors = {
+ email: '',
+ password: ''
+ };
+
+ let hasError = false;
+
+ // 验证邮箱
+ if (!formData.email || typeof formData.email !== 'string' || !formData.email.trim()) {
+ newErrors.email = '请输入邮箱地址';
+ hasError = true;
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+ newErrors.email = '请输入有效的邮箱地址';
+ hasError = true;
+ }
+
+ // 验证密码
+ if (!formData.password || typeof formData.password !== 'string' || !formData.password.trim()) {
+ newErrors.password = '请输入密码';
+ hasError = true;
+ } else if (formData.password.length < 6) {
+ newErrors.password = '密码长度至少6位';
+ hasError = true;
+ }
+
+ setErrors(newErrors);
+ return !hasError;
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ // 验证表单
+ if (!validateForm()) {
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ // 发送登录请求到后端
+ const response = await fetch(baseURL + '/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ email: formData.email, // 后端支持邮箱登录
+ password: hashPassword(formData.password) // 前端加密密码
+ })
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ // 显示成功提示
+ showSuccessAlert('登录成功!正在跳转...');
+
+ // 保存认证信息
+ saveAuthInfo(result.token, result.user, rememberMe);
+
+ // 保存或清除记住的登录信息
+ saveRememberedLoginInfo(formData.email, formData.password, rememberMe);
+
+ // 延迟跳转,让用户看到成功提示
+ setTimeout(() => {
+ window.location.href = '/test-dashboard';
+ }, 1500);
+ } else {
+ // 登录失败,显示错误信息
+ let errorTitle = '登录失败';
+ let errorContent = result.message || '登录失败,请检查您的邮箱和密码';
+
+ // 根据错误类型提供更详细的信息
+ if (result.message) {
+ if (result.message.includes('邮箱') || result.message.includes('email')) {
+ errorTitle = '邮箱验证失败';
+ errorContent = '您输入的邮箱地址不存在或格式不正确,请检查后重试。';
+ } else if (result.message.includes('密码') || result.message.includes('password')) {
+ errorTitle = '密码验证失败';
+ errorContent = '您输入的密码不正确,请检查后重试。如果忘记密码,请点击"忘记密码"进行重置。';
+ } else if (result.message.includes('用户不存在')) {
+ errorTitle = '用户不存在';
+ errorContent = '该邮箱尚未注册,请先注册账户或检查邮箱地址是否正确。';
+ } else if (result.message.includes('账户被锁定') || result.message.includes('locked')) {
+ errorTitle = '账户被锁定';
+ errorContent = '您的账户因安全原因被暂时锁定,请联系客服或稍后重试。';
+ }
+ }
+
+ showErrorModal(errorTitle, errorContent);
+ }
+ } catch (error) {
+ console.error('登录请求失败:', error);
+
+ // 根据错误类型显示不同的错误信息
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
+ showErrorModal('网络连接失败', '无法连接到服务器,请检查您的网络连接后重试。如果问题持续存在,请联系客服。');
+ } else if (error.name === 'AbortError') {
+ showErrorModal('请求超时', '请求超时,请检查网络连接后重试。');
+ } else {
+ showErrorModal('登录失败', '网络连接失败,请检查网络或稍后重试。如果问题持续存在,请联系客服。');
+ }
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <div className="login-container">
+ <div className="login-background"></div>
+
+ {isLoading && (
+ <div className="loading-overlay">
+ <div className="loading-content">
+ <div className="loading-spinner-large"></div>
+ <p className="loading-text">正在登录...</p>
+ </div>
+ </div>
+ )}
+
+ <div className="login-content">
+ <div className="login-card">
+ {/* 成功提示 */}
+ {successAlert.visible && (
+ <div style={{ marginBottom: '16px' }}>
+ <Alert
+ message={successAlert.message}
+ type="success"
+ icon={<CheckCircleOutlined />}
+ showIcon
+ closable
+ onClose={() => setSuccessAlert({ visible: false, message: '' })}
+ style={{
+ borderRadius: '8px',
+ border: '1px solid #b7eb8f',
+ backgroundColor: '#f6ffed'
+ }}
+ />
+ </div>
+ )}
+
+ <div className="login-header">
+ <h1 className="login-title">欢迎来到小红书</h1>
+ <p className="login-subtitle">标记我的生活</p>
+ </div>
+
+ <form className="login-form" onSubmit={handleSubmit}>
+ <div className="form-group">
+ <Input
+ type="email"
+ id="email"
+ name="email"
+ className={`form-input ${errors.email ? 'input-error' : ''}`}
+ placeholder="请输入您的邮箱"
+ value={formData.email}
+ onChange={handleEmailChange}
+ prefix={<MailOutlined />}
+ size="large"
+ title=""
+ status={errors.email ? 'error' : ''}
+ />
+ {errors.email && (
+ <div className="error-message">
+ {errors.email}
+ </div>
+ )}
+ </div>
+
+ <div className="form-group">
+ <Input.Password
+ id="password"
+ name="password"
+ className={`form-input ${errors.password ? 'input-error' : ''}`}
+ placeholder="请输入您的密码"
+ value={formData.password}
+ onChange={handlePasswordChange}
+ prefix={<LockOutlined />}
+ size="large"
+ title=""
+ status={errors.password ? 'error' : ''}
+ />
+ {errors.password && (
+ <div className="error-message">
+ {errors.password}
+ </div>
+ )}
+ </div>
+
+ <div className="form-options">
+ <Checkbox
+ checked={rememberMe}
+ onChange={handleRememberMeChange}
+ >
+ 记住我
+ </Checkbox>
+ <Link to="/forgot-password" className="forgot-password">忘记密码?</Link>
+ </div>
+
+ <button
+ type="submit"
+ className={`login-button ${isLoading ? 'loading' : ''}`}
+ disabled={isLoading}
+ >
+ {isLoading ? (
+ <>
+ <div className="loading-spinner"></div>
+ 登录中...
+ </>
+ ) : (
+ '登录'
+ )}
+ </button>
+ </form>
+
+ <div className="signup-link">
+ <p>还没有账户? <Link to="/register">立即注册</Link></p>
+ </div>
+ </div>
+ </div>
+
+ {/* 错误弹窗 */}
+ <Modal
+ title={
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+ <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '18px' }} />
+ {errorModal.title}
+ </div>
+ }
+ open={errorModal.visible}
+ onOk={closeErrorModal}
+ onCancel={closeErrorModal}
+ okText="我知道了"
+ cancelButtonProps={{ style: { display: 'none' } }}
+ centered
+ className="error-modal"
+ >
+ <div style={{ padding: '16px 0', fontSize: '14px', lineHeight: '1.6' }}>
+ {errorModal.content}
+ </div>
+ </Modal>
+ </div>
+ );
+};
+
+export default LoginPage;