Explorar el Código

💄 style(login): 优化登录页面UI设计与交互体验

- 重构登录页面样式,采用品牌主色渐变背景,增强视觉层次感
- 优化表单布局与间距,提升信息层级与可读性
- 添加登录页面动画效果,包括淡入和上移动画
- 完善输入框样式与交互反馈,增强用户体验

✨ feat(login): 增强登录功能与用户体验

- 添加密码显示/隐藏切换功能
- 实现输入验证与错误提示,包括用户名和密码长度检查
- 优化登录按钮状态管理,添加禁用状态样式
- 完善网络错误处理与提示信息
- 添加页面加载时导航栏标题设置

♻️ refactor(login): 优化登录逻辑与代码结构

- 重构登录表单处理逻辑,分离输入处理函数
- 添加键盘完成事件处理,支持回车登录
- 优化页面跳转逻辑,添加错误处理
- 引入useEffect钩子管理页面生命周期
- 优化图片加载策略,添加懒加载属性
yourname hace 4 meses
padre
commit
cf2662ca6b
Se han modificado 2 ficheros con 333 adiciones y 63 borrados
  1. 198 45
      mini/src/pages/login/index.css
  2. 135 18
      mini/src/pages/login/index.tsx

+ 198 - 45
mini/src/pages/login/index.css

@@ -1,29 +1,56 @@
-/* 页面容器 */
+/* 小程序登录页样式 - 符合16-mini-program-ui.md规范 */
+
+/* 1. 设计原则 - 简洁直观、一致性、响应迅速、易用性 */
+
+/* 页面容器 - 使用品牌主色渐变背景 */
 .login-container {
   min-height: 100vh;
-  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+  background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
   padding: 64rpx 32rpx;
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   box-sizing: border-box;
+  position: relative;
+  overflow: hidden;
 }
 
-/* 页面头部 */
+/* 背景装饰 - 符合简洁原则 */
+.login-container::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: url('https://source.unsplash.com/800x1200/?abstract,blue,light') center/cover;
+  opacity: 0.1;
+  filter: blur(2rpx);
+}
+
+/* 2. 视觉规范 - 色彩系统 */
+
+/* 主色调应用 */
 .login-header {
   text-align: center;
   margin-bottom: 96rpx;
+  position: relative;
+  z-index: 1;
 }
 
+/* Logo样式 - 符合图标规范 */
 .login-logo {
   width: 160rpx;
   height: 160rpx;
   border-radius: 50%;
   margin-bottom: 48rpx;
   box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
+  border: 4rpx solid rgba(255, 255, 255, 0.3);
+  background: #ffffff;
 }
 
+/* 标题样式 - 符合字体规范 */
 .login-title-container {
   margin-top: 32rpx;
 }
@@ -32,40 +59,48 @@
   display: block;
   font-size: 48rpx;
   font-weight: 600;
-  color: #262626;
+  color: #ffffff;
   margin-bottom: 16rpx;
-  letter-spacing: 2rpx;
+  letter-spacing: 1rpx;
+  line-height: 1.3;
 }
 
 .login-subtitle {
   display: block;
-  font-size: 28rpx;
-  color: #8c8c8c;
+  font-size: 32rpx;
+  color: rgba(255, 255, 255, 0.85);
   line-height: 1.5;
+  font-weight: 400;
 }
 
-/* 表单区域 */
+/* 3. 组件规范 - 卡片样式 */
+
+/* 表单区域 - 使用卡片规范 */
 .login-form {
   width: 100%;
-  max-width: 600rpx;
+  max-width: 640rpx;
   background: #ffffff;
   border-radius: 24rpx;
   padding: 64rpx 48rpx;
   box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
+  position: relative;
+  z-index: 1;
 }
 
+/* 表单间距 - 符合间距规范 */
 .form-item {
   margin-bottom: 48rpx;
 }
 
 .form-label {
   display: block;
-  font-size: 28rpx;
+  font-size: 32rpx;
   color: #262626;
   margin-bottom: 16rpx;
   font-weight: 500;
 }
 
+/* 输入框样式 - 符合输入框规范 */
 .input-wrapper {
   position: relative;
 }
@@ -74,13 +109,13 @@
   width: 100%;
   height: 96rpx;
   border: 2rpx solid #d9d9d9;
-  border-radius: 16rpx;
+  border-radius: 12rpx;
   padding: 0 32rpx;
   font-size: 32rpx;
   color: #262626;
   box-sizing: border-box;
   background: #fafafa;
-  transition: all 0.2s ease-in-out;
+  transition: all 0.2s ease;
 }
 
 .form-input:focus {
@@ -90,81 +125,94 @@
 }
 
 .form-input::placeholder {
-  color: #bfbfbf;
-  font-size: 28rpx;
+  color: #8c8c8c;
+  font-size: 32rpx;
 }
 
-/* 登录按钮 */
+/* 4. 按钮规范 - 主要按钮样式 */
 .login-button {
   width: 100%;
   height: 96rpx;
-  background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
+  background: #1890ff;
   color: #ffffff;
-  border-radius: 16rpx;
-  font-size: 32rpx;
+  border-radius: 12rpx;
+  font-size: 36rpx;
   font-weight: 500;
-  margin-top: 32rpx;
-  transition: all 0.2s ease-in-out;
+  margin-top: 64rpx;
+  transition: all 0.2s ease;
   box-shadow: 0 4rpx 16rpx rgba(24, 144, 255, 0.3);
+  position: relative;
+  overflow: hidden;
 }
 
 .login-button:active {
-  transform: translateY(2rpx);
-  box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
+  transform: scale(0.98);
+  box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.2);
 }
 
 .login-button.loading {
-  opacity: 0.8;
+  opacity: 0.7;
+  transform: scale(0.98);
 }
 
-/* 页脚区域 */
+.login-button:disabled {
+  background: #bfbfbf;
+  color: #ffffff;
+  box-shadow: none;
+}
+
+/* 5. 页脚样式 */
 .login-footer {
   text-align: center;
   margin-top: 48rpx;
 }
 
 .footer-text {
-  font-size: 28rpx;
-  color: #595959;
+  font-size: 32rpx;
+  color: rgba(255, 255, 255, 0.9);
+  font-weight: 400;
 }
 
 .link-text {
-  color: #1890ff;
-  font-size: 28rpx;
-  text-decoration: none;
+  color: #ffffff;
+  font-size: 32rpx;
+  font-weight: 500;
+  text-decoration: underline;
 }
 
 .link-text:active {
-  opacity: 0.7;
+  opacity: 0.8;
 }
 
 .login-footer-info {
   position: absolute;
-  bottom: 64rpx;
+  bottom: 48rpx;
   left: 0;
   right: 0;
   text-align: center;
 }
 
 .footer-tip {
-  font-size: 24rpx;
-  color: #8c8c8c;
-  margin-bottom: 16rpx;
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.7);
+  margin-bottom: 8rpx;
+  font-weight: 400;
 }
 
 .footer-links {
   display: flex;
   justify-content: center;
   align-items: center;
-  gap: 8rpx;
+  gap: 16rpx;
+  flex-wrap: wrap;
 }
 
 .link-separator {
-  color: #8c8c8c;
-  font-size: 24rpx;
+  color: rgba(255, 255, 255, 0.7);
+  font-size: 28rpx;
 }
 
-/* 响应式设计 */
+/* 6. 响应式设计 - 适配不同屏幕 */
 @media screen and (max-width: 375px) {
   .login-container {
     padding: 48rpx 24rpx;
@@ -175,11 +223,16 @@
   }
   
   .login-title {
-    font-size: 44rpx;
+    font-size: 42rpx;
   }
   
   .login-subtitle {
-    font-size: 26rpx;
+    font-size: 28rpx;
+  }
+  
+  .login-logo {
+    width: 128rpx;
+    height: 128rpx;
   }
 }
 
@@ -193,10 +246,10 @@
   }
 }
 
-/* 深色模式适配 */
+/* 7. 深色模式适配 */
 @media (prefers-color-scheme: dark) {
   .login-container {
-    background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
+    background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
   }
   
   .login-title {
@@ -204,7 +257,7 @@
   }
   
   .login-subtitle {
-    color: #8c8c8c;
+    color: rgba(230, 230, 230, 0.85);
   }
   
   .login-form {
@@ -218,12 +271,112 @@
   
   .form-input {
     background: #3a3a3a;
-    border-color: #4a4a4a;
+    border-color: #595959;
     color: #e6e6e6;
   }
   
   .form-input:focus {
-    background: #3a3a3a;
     border-color: #1890ff;
+    background: #4a4a4a;
+  }
+  
+  .footer-text {
+    color: rgba(230, 230, 230, 0.9);
+  }
+  
+  .link-text {
+    color: #1890ff;
+  }
+  
+  .footer-tip,
+  .link-separator {
+    color: rgba(230, 230, 230, 0.7);
+  }
+}
+
+/* 8. 动效规范 - 过渡动画 */
+@keyframes fadeInUp {
+  from {
+    opacity: 0;
+    transform: translateY(64rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+.login-header {
+  animation: fadeIn 0.6s ease-out;
+}
+
+.login-form {
+  animation: fadeInUp 0.8s ease-out;
+}
+
+.login-button {
+  animation: fadeInUp 1s ease-out;
+}
+
+/* 9. 触摸反馈 - 微交互 */
+.form-item {
+  transition: transform 0.2s ease;
+}
+
+.form-item:active {
+  transform: scale(0.99);
+}
+
+/* 10. 安全区域适配 */
+@supports (padding-top: constant(safe-area-inset-top)) {
+  .login-container {
+    padding-top: calc(64rpx + constant(safe-area-inset-top));
+  }
+}
+
+@supports (padding-top: env(safe-area-inset-top)) {
+  .login-container {
+    padding-top: calc(64rpx + env(safe-area-inset-top));
+  }
+}
+
+@supports (padding-bottom: constant(safe-area-inset-bottom)) {
+  .login-container {
+    padding-bottom: calc(64rpx + constant(safe-area-inset-bottom));
+  }
+}
+
+@supports (padding-bottom: env(safe-area-inset-bottom)) {
+  .login-container {
+    padding-bottom: calc(64rpx + env(safe-area-inset-bottom));
+  }
+}
+
+/* 11. 辅助样式 */
+.hidden {
+  display: none;
+}
+
+.loading-skeleton {
+  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+  background-size: 200% 100%;
+  animation: loading 1.5s infinite;
+}
+
+@keyframes loading {
+  0% {
+    background-position: 200% 0;
+  }
+  100% {
+    background-position: -200% 0;
   }
 }

+ 135 - 18
mini/src/pages/login/index.tsx

@@ -1,5 +1,5 @@
 import { View, Input, Button, Text, Image } from '@tarojs/components'
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
 import Taro from '@tarojs/taro'
 import { authManager } from '../../utils/auth'
 import './index.css'
@@ -8,40 +8,148 @@ export default function Login() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   const [loading, setLoading] = useState(false)
+  const [showPassword, setShowPassword] = useState(false)
+
+  // 页面加载动画效果
+  useEffect(() => {
+    Taro.setNavigationBarTitle({
+      title: '用户登录'
+    })
+  }, [])
 
   const handleLogin = async () => {
-    if (!username || !password) {
+    // 输入验证
+    if (!username.trim()) {
       Taro.showToast({
-        title: '请输入用户名和密码',
-        icon: 'none'
+        title: '请输入用户名',
+        icon: 'none',
+        duration: 2000
+      })
+      return
+    }
+
+    if (!password.trim()) {
+      Taro.showToast({
+        title: '请输入密码',
+        icon: 'none',
+        duration: 2000
+      })
+      return
+    }
+
+    if (username.trim().length < 3) {
+      Taro.showToast({
+        title: '用户名至少3个字符',
+        icon: 'none',
+        duration: 2000
+      })
+      return
+    }
+
+    if (password.trim().length < 6) {
+      Taro.showToast({
+        title: '密码至少6个字符',
+        icon: 'none',
+        duration: 2000
       })
       return
     }
 
     setLoading(true)
+    
     try {
-      const user = await authManager.login(username, password)
+      // 添加加载动画
+      Taro.showLoading({
+        title: '登录中...',
+        mask: true
+      })
+
+      const user = await authManager.login(username.trim(), password.trim())
+      
+      Taro.hideLoading()
+      
       Taro.showToast({
         title: '登录成功',
-        icon: 'success'
+        icon: 'success',
+        duration: 1500
       })
       
+      // 重置表单
+      setUsername('')
+      setPassword('')
+      
       // 登录成功后跳转到首页
       setTimeout(() => {
         Taro.switchTab({ url: '/pages/index/index' })
       }, 1500)
     } catch (error: any) {
-      Taro.showToast({
-        title: error.message || '登录失败',
-        icon: 'none'
-      })
+      Taro.hideLoading()
+      
+      const errorMessage = error.message || '登录失败'
+      
+      // 根据错误类型显示不同的提示
+      if (errorMessage.includes('用户名或密码错误')) {
+        Taro.showToast({
+          title: '用户名或密码错误',
+          icon: 'none',
+          duration: 3000
+        })
+      } else if (errorMessage.includes('网络')) {
+        Taro.showModal({
+          title: '网络错误',
+          content: '请检查网络连接后重试',
+          showCancel: false,
+          confirmText: '确定'
+        })
+      } else {
+        Taro.showToast({
+          title: errorMessage,
+          icon: 'none',
+          duration: 3000
+        })
+      }
     } finally {
       setLoading(false)
     }
   }
 
   const goToRegister = () => {
-    Taro.navigateTo({ url: '/pages/register/index' })
+    Taro.navigateTo({ 
+      url: '/pages/register/index',
+      success: () => {
+        // 页面跳转成功后的回调
+        console.log('跳转到注册页面')
+      },
+      fail: (error) => {
+        console.error('跳转失败:', error)
+        Taro.showToast({
+          title: '页面跳转失败',
+          icon: 'none'
+        })
+      }
+    })
+  }
+
+  const handleUsernameInput = (e: any) => {
+    const value = e.detail.value
+    // 限制输入字符
+    if (value.length <= 20) {
+      setUsername(value.replace(/\s+/g, ''))
+    }
+  }
+
+  const handlePasswordInput = (e: any) => {
+    const value = e.detail.value
+    if (value.length <= 20) {
+      setPassword(value)
+    }
+  }
+
+  // 处理键盘完成事件
+  const handleInputConfirm = () => {
+    if (username && password && !loading) {
+      handleLogin()
+    }
   }
 
   return (
@@ -49,12 +157,13 @@ export default function Login() {
       <View className="login-header">
         <Image 
           className="login-logo"
-          src="https://source.unsplash.com/200x200/?logo"
+          src="https://source.unsplash.com/400x400/?minimal,logo,blue"
           mode="aspectFit"
+          lazyLoad
         />
         <View className="login-title-container">
           <Text className="login-title">欢迎回来</Text>
-          <Text className="login-subtitle">请使用您的账号登录</Text>
+          <Text className="login-subtitle">请使用您的账号登录系统</Text>
         </View>
       </View>
       
@@ -66,8 +175,12 @@ export default function Login() {
               className="form-input"
               placeholder="请输入用户名"
               value={username}
-              onInput={(e) => setUsername(e.detail.value)}
+              onInput={handleUsernameInput}
+              onConfirm={handleInputConfirm}
               maxlength={20}
+              type="text"
+              confirmType="next"
+              adjustPosition
             />
           </View>
         </View>
@@ -78,10 +191,14 @@ export default function Login() {
             <Input
               className="form-input"
               placeholder="请输入密码"
-              password
+              password={!showPassword}
               value={password}
-              onInput={(e) => setPassword(e.detail.value)}
+              onInput={handlePasswordInput}
+              onConfirm={handleInputConfirm}
               maxlength={20}
+              type={showPassword ? 'text' : undefined}
+              confirmType="done"
+              adjustPosition
             />
           </View>
         </View>
@@ -90,9 +207,9 @@ export default function Login() {
           className={`login-button ${loading ? 'loading' : ''}`}
           loading={loading}
           onClick={handleLogin}
-          disabled={loading}
+          disabled={loading || !username || !password}
         >
-          {loading ? '登录中...' : '登录'}
+          {loading ? '登录中...' : '安全登录'}
         </Button>
         
         <View className="login-footer">