ソースを参照

✨ feat(ui): 统一页面UI组件与样式

- 引入自定义Input和Button组件替代原生组件,增强UI一致性
- 为登录、注册和个人资料页面添加统一的输入框样式
- 实现带图标输入框,支持左右图标和点击交互
- 优化按钮样式和状态管理,统一尺寸和交互效果

♻️ refactor(profile): 优化个人资料页面数据处理

- 移除冗余的UserProfile接口定义,使用auth模块类型
- 优化用户头像加载逻辑,更新默认头像URL
- 修复最近登录时间显示问题,使用updatedAt字段替代lastLoginAt
- 优化用户信息展示布局,增强视觉层次

💄 style: 优化页面样式和布局

- 为首页添加index.css样式文件
- 统一调整表单元素间距和排版
- 优化按钮禁用状态样式
- 调整个人资料页面用户信息卡片布局
yourname 4 ヶ月 前
コミット
7d48313b33

+ 1 - 0
mini/src/pages/index/index.tsx

@@ -3,6 +3,7 @@ import { View, Text } from '@tarojs/components'
 import { TabBarLayout } from '@/layouts/tab-bar-layout'
 import { Input } from '@/components/ui/input'
 import { Label } from '@/components/ui/label'
+import './index.css'
 
 const HomePage: React.FC = () => {
   return (

+ 33 - 43
mini/src/pages/login/index.tsx

@@ -1,8 +1,10 @@
-import { View, Input, Button, Text, Image } from '@tarojs/components'
+import { View, Text } from '@tarojs/components'
 import { useState, useEffect } from 'react'
 import Taro from '@tarojs/taro'
 import { useAuth } from '@/utils/auth'
 import { cn } from '@/utils/cn'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
 
 export default function Login() {
   const [username, setUsername] = useState('')
@@ -143,49 +145,36 @@ export default function Login() {
             {/* 用户名输入框 */}
             <View className="space-y-2">
               <Text className="text-sm font-medium text-gray-700">用户名</Text>
-              <View className="relative">
-                <View className="absolute left-3 top-1/2 -translate-y-1/2">
-                  <View className="i-heroicons-user-20-solid w-5 h-5 text-gray-400" />
-                </View>
-                <Input
-                  className="w-full h-12 pl-10 pr-4 bg-gray-50 rounded-lg text-gray-900 placeholder-gray-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
-                  placeholder="请输入用户名"
-                  value={username}
-                  onInput={handleUsernameInput}
-                  maxlength={20}
-                  type="text"
-                  confirmType="next"
-                />
-              </View>
+              <Input
+                leftIcon="i-heroicons-user-20-solid"
+                placeholder="请输入用户名"
+                value={username}
+                onInput={handleUsernameInput}
+                maxlength={20}
+                type="text"
+                confirmType="next"
+                size="lg"
+                variant="filled"
+              />
             </View>
 
             {/* 密码输入框 */}
             <View className="space-y-2">
               <Text className="text-sm font-medium text-gray-700">密码</Text>
-              <View className="relative">
-                <View className="absolute left-3 top-1/2 -translate-y-1/2">
-                  <View className="i-heroicons-lock-closed-20-solid w-5 h-5 text-gray-400" />
-                </View>
-                <Input
-                  className="w-full h-12 pl-10 pr-12 bg-gray-50 rounded-lg text-gray-900 placeholder-gray-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
-                  placeholder="请输入密码"
-                  password={!showPassword}
-                  value={password}
-                  onInput={handlePasswordInput}
-                  maxlength={20}
-                  type={showPassword ? 'text' : 'password'}
-                  confirmType="done"
-                />
-                <View 
-                  className="absolute right-3 top-1/2 -translate-y-1/2"
-                  onClick={() => setShowPassword(!showPassword)}
-                >
-                  <View className={cn(
-                    "w-5 h-5 text-gray-400",
-                    showPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"
-                  )} />
-                </View>
-              </View>
+              <Input
+                leftIcon="i-heroicons-lock-closed-20-solid"
+                rightIcon={showPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"}
+                placeholder="请输入密码"
+                password={!showPassword}
+                value={password}
+                onInput={handlePasswordInput}
+                maxlength={20}
+                type={showPassword ? 'text' : 'password'}
+                confirmType="done"
+                size="lg"
+                variant="filled"
+                onRightIconClick={() => setShowPassword(!showPassword)}
+              />
             </View>
 
             {/* 忘记密码 */}
@@ -196,12 +185,13 @@ export default function Login() {
             {/* 登录按钮 */}
             <Button
               className={cn(
-                "w-full h-12 rounded-lg font-medium text-white transition-all duration-200",
+                "w-full",
                 isLoading || !username || !password
-                  ? "bg-gray-300 cursor-not-allowed"
-                  : "bg-blue-500 hover:bg-blue-600 active:bg-blue-700 shadow-lg"
+                  ? "bg-gray-300"
+                  : "bg-blue-500 hover:bg-blue-600"
               )}
-              loading={isLoading}
+              size="lg"
+              variant="default"
               onClick={handleLogin}
               disabled={isLoading || !username || !password}
             >

+ 18 - 25
mini/src/pages/profile/index.tsx

@@ -1,18 +1,11 @@
 import { useState, useEffect } from 'react'
-import { View, Text, Image, Button, ScrollView } from '@tarojs/components'
+import { View, Text, ScrollView } from '@tarojs/components'
 import Taro from '@tarojs/taro'
 import { TabBarLayout } from '@/layouts/tab-bar-layout'
-import { useAuth } from '@/utils/auth'
+import { useAuth , type User as UserProfile} from '@/utils/auth'
 import { cn } from '@/utils/cn'
-
-interface UserProfile {
-  id: number
-  username: string
-  email?: string
-  avatar?: string
-  createdAt: string
-  lastLoginAt?: string
-}
+import { Button } from '@/components/ui/button'
+import { Image } from '@/components/ui/image'
 
 const ProfilePage: React.FC = () => {
   const [userProfile, setUserProfile] = useState<UserProfile | null>(null)
@@ -22,12 +15,8 @@ const ProfilePage: React.FC = () => {
   useEffect(() => {
     if (user) {
       setUserProfile({
-        id: user.id,
-        username: user.username,
-        email: user.email,
-        avatar: user.avatar || 'https://source.unsplash.com/200x200/?avatar,face',
-        createdAt: new Date().toISOString(),
-        lastLoginAt: new Date().toISOString()
+        ...user,
+        avatar: user.avatar || 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=160&h=160&fit=crop&crop=face',
       })
     }
     setLoading(false)
@@ -122,7 +111,8 @@ const ProfilePage: React.FC = () => {
             <View className="i-heroicons-exclamation-circle-20-solid w-12 h-12 text-gray-400 mx-auto mb-4" />
             <Text className="text-gray-600 mb-4">请先登录</Text>
             <Button
-              className="bg-blue-500 text-white px-6 py-2 rounded-lg"
+              variant="default"
+              size="lg"
               onClick={() => Taro.navigateTo({ url: '/pages/login/index' })}
             >
               去登录
@@ -141,10 +131,11 @@ const ProfilePage: React.FC = () => {
           <View className="flex flex-col items-center pt-8 pb-6">
             <View className="relative">
               <Image
-                className="w-24 h-24 rounded-full border-4 border-white shadow-lg"
-                src={userProfile.avatar || 'https://source.unsplash.com/200x200/?avatar'}
-                mode="aspectFill"
-              />
+               className="w-24 h-24 border-4 border-white shadow-lg"
+               src={userProfile.avatar || 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=160&h=160&fit=crop&crop=face'}
+               mode="aspectFill"
+               rounded="full"
+             />
               <View className="absolute -bottom-2 -right-2 w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center shadow-md">
                 <View className="i-heroicons-camera-20-solid w-4 h-4 text-white" />
               </View>
@@ -206,11 +197,11 @@ const ProfilePage: React.FC = () => {
                 <Text className="text-sm text-gray-600">用户ID</Text>
                 <Text className="text-sm text-gray-900 font-mono">{userProfile.id}</Text>
               </View>
-              {userProfile.lastLoginAt && (
+              {userProfile.updatedAt && (
                 <View className="flex justify-between items-center">
                   <Text className="text-sm text-gray-600">最近登录</Text>
                   <Text className="text-sm text-gray-900">
-                    {new Date(userProfile.lastLoginAt).toLocaleString('zh-CN')}
+                    {new Date(userProfile.updatedAt).toLocaleString('zh-CN')}
                   </Text>
                 </View>
               )}
@@ -221,7 +212,9 @@ const ProfilePage: React.FC = () => {
         {/* 退出登录按钮 */}
         <View className="px-4 pt-6 pb-8">
           <Button
-            className="w-full h-12 bg-red-500 text-white rounded-xl font-medium hover:bg-red-600 active:bg-red-700 transition-colors duration-200"
+            variant="destructive"
+            size="lg"
+            className="w-full"
             onClick={handleLogout}
           >
             <View className="flex items-center justify-center">

+ 51 - 74
mini/src/pages/register/index.tsx

@@ -1,8 +1,10 @@
-import { View, Input, Button, Text, Image } from '@tarojs/components'
+import { View, Text } from '@tarojs/components'
 import { useState, useEffect } from 'react'
 import Taro from '@tarojs/taro'
 import { useAuth } from '@/utils/auth'
 import { cn } from '@/utils/cn'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
 
 export default function Register() {
   const [username, setUsername] = useState('')
@@ -132,101 +134,76 @@ export default function Register() {
             {/* 用户名输入框 */}
             <View className="space-y-2">
               <Text className="text-sm font-medium text-gray-700">用户名</Text>
-              <View className="relative">
-                <View className="absolute left-3 top-1/2 -translate-y-1/2">
-                  <View className="i-heroicons-user-20-solid w-5 h-5 text-gray-400" />
-                </View>
-                <Input
-                  className="w-full h-12 pl-10 pr-4 bg-gray-50 rounded-lg text-gray-900 placeholder-gray-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
-                  placeholder="请输入用户名(3-20个字符)"
-                  value={username}
-                  onInput={(e) => setUsername(e.detail.value)}
-                  maxlength={20}
-                />
-              </View>
+              <Input
+                leftIcon="i-heroicons-user-20-solid"
+                placeholder="请输入用户名(3-20个字符)"
+                value={username}
+                onInput={(e) => setUsername(e.detail.value)}
+                maxlength={20}
+                size="lg"
+                variant="filled"
+              />
             </View>
 
             {/* 邮箱输入框 */}
             <View className="space-y-2">
               <Text className="text-sm font-medium text-gray-700">邮箱(可选)</Text>
-              <View className="relative">
-                <View className="absolute left-3 top-1/2 -translate-y-1/2">
-                  <View className="i-heroicons-envelope-20-solid w-5 h-5 text-gray-400" />
-                </View>
-                <Input
-                  className="w-full h-12 pl-10 pr-4 bg-gray-50 rounded-lg text-gray-900 placeholder-gray-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
-                  placeholder="请输入邮箱地址"
-                  type="text"
-                  value={email}
-                  onInput={(e) => setEmail(e.detail.value)}
-                  maxlength={50}
-                />
-              </View>
+              <Input
+                leftIcon="i-heroicons-envelope-20-solid"
+                placeholder="请输入邮箱地址"
+                type="text"
+                value={email}
+                onInput={(e) => setEmail(e.detail.value)}
+                maxlength={50}
+                size="lg"
+                variant="filled"
+              />
             </View>
 
             {/* 密码输入框 */}
             <View className="space-y-2">
               <Text className="text-sm font-medium text-gray-700">密码</Text>
-              <View className="relative">
-                <View className="absolute left-3 top-1/2 -translate-y-1/2">
-                  <View className="i-heroicons-lock-closed-20-solid w-5 h-5 text-gray-400" />
-                </View>
-                <Input
-                  className="w-full h-12 pl-10 pr-12 bg-gray-50 rounded-lg text-gray-900 placeholder-gray-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
-                  placeholder="请输入密码(至少6位)"
-                  password={!showPassword}
-                  value={password}
-                  onInput={(e) => setPassword(e.detail.value)}
-                  maxlength={20}
-                />
-                <View 
-                  className="absolute right-3 top-1/2 -translate-y-1/2"
-                  onClick={() => setShowPassword(!showPassword)}
-                >
-                  <View className={cn(
-                    "w-5 h-5 text-gray-400",
-                    showPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"
-                  )} />
-                </View>
-              </View>
+              <Input
+                leftIcon="i-heroicons-lock-closed-20-solid"
+                rightIcon={showPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"}
+                placeholder="请输入密码(至少6位)"
+                password={!showPassword}
+                value={password}
+                onInput={(e) => setPassword(e.detail.value)}
+                maxlength={20}
+                size="lg"
+                variant="filled"
+                onRightIconClick={() => setShowPassword(!showPassword)}
+              />
             </View>
 
             {/* 确认密码输入框 */}
             <View className="space-y-2">
               <Text className="text-sm font-medium text-gray-700">确认密码</Text>
-              <View className="relative">
-                <View className="absolute left-3 top-1/2 -translate-y-1/2">
-                  <View className="i-heroicons-lock-closed-20-solid w-5 h-5 text-gray-400" />
-                </View>
-                <Input
-                  className="w-full h-12 pl-10 pr-12 bg-gray-50 rounded-lg text-gray-900 placeholder-gray-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
-                  placeholder="请再次输入密码"
-                  password={!showConfirmPassword}
-                  value={confirmPassword}
-                  onInput={(e) => setConfirmPassword(e.detail.value)}
-                  maxlength={20}
-                />
-                <View 
-                  className="absolute right-3 top-1/2 -translate-y-1/2"
-                  onClick={() => setShowConfirmPassword(!showConfirmPassword)}
-                >
-                  <View className={cn(
-                    "w-5 h-5 text-gray-400",
-                    showConfirmPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"
-                  )} />
-                </View>
-              </View>
+              <Input
+                leftIcon="i-heroicons-lock-closed-20-solid"
+                rightIcon={showConfirmPassword ? "i-heroicons-eye-20-solid" : "i-heroicons-eye-slash-20-solid"}
+                placeholder="请再次输入密码"
+                password={!showConfirmPassword}
+                value={confirmPassword}
+                onInput={(e) => setConfirmPassword(e.detail.value)}
+                maxlength={20}
+                size="lg"
+                variant="filled"
+                onRightIconClick={() => setShowConfirmPassword(!showConfirmPassword)}
+              />
             </View>
 
             {/* 注册按钮 */}
             <Button
               className={cn(
-                "w-full h-12 rounded-lg font-medium text-white transition-all duration-200",
+                "w-full",
                 isLoading || !username || !password || !confirmPassword
-                  ? "bg-gray-300 cursor-not-allowed"
-                  : "bg-green-500 hover:bg-green-600 active:bg-green-700 shadow-lg"
+                  ? "bg-gray-300"
+                  : "bg-green-500 hover:bg-green-600"
               )}
-              loading={isLoading}
+              size="lg"
+              variant="default"
               onClick={handleRegister}
               disabled={isLoading || !username || !password || !confirmPassword}
             >