Bläddra i källkod

✨ feat(mini): 实现小程序用户认证功能

- 添加登录页和注册页
- 实现用户登录和注册功能
- 添加用户认证状态管理
- 完善首页UI,根据登录状态显示不同内容

📝 docs(mini): 添加小程序项目文档

- 创建README.md,包含项目介绍和使用说明
- 记录项目结构和API接口信息
- 添加环境配置和快速开始指南

💄 style(mini): 优化小程序UI样式

- 设计登录和注册页面样式
- 美化首页布局和交互效果
- 统一导航栏样式和颜色

♻️ refactor(mini): 重构小程序项目结构

- 添加API封装和认证工具类
- 优化页面路由配置
- 完善用户状态管理逻辑
yourname 4 månader sedan
förälder
incheckning
dcddbb7e5c

+ 190 - 0
mini/README.md

@@ -0,0 +1,190 @@
+# 小程序用户认证Starter
+
+这是一个基于Taro的小程序用户认证starter项目,集成了完整的用户登录、注册功能,并连接到后端API。
+
+## 功能特性
+
+- ✅ 用户注册和登录
+- ✅ JWT Token认证
+- ✅ 用户信息管理
+- ✅ 响应式设计
+- ✅ 错误处理
+- ✅ 本地存储
+
+## 技术栈
+
+- **框架**: Taro 4.1.4
+- **语言**: TypeScript
+- **样式**: CSS
+- **HTTP请求**: Taro.request
+- **状态管理**: 本地存储
+
+## 项目结构
+
+```
+mini/
+├── src/
+│   ├── pages/
+│   │   ├── index/          # 首页
+│   │   ├── login/          # 登录页
+│   │   └── register/       # 注册页
+│   ├── utils/
+│   │   ├── api.ts          # API封装
+│   │   └── auth.ts         # 认证工具
+│   └── app.config.ts       # 小程序配置
+├── config/
+│   ├── dev.ts              # 开发配置
+│   ├── prod.ts             # 生产配置
+│   └── index.ts            # 通用配置
+├── .env.development        # 开发环境变量
+├── .env.production         # 生产环境变量
+└── package.json
+```
+
+## 快速开始
+
+### 1. 安装依赖
+
+```bash
+cd mini
+pnpm install
+```
+
+### 2. 配置环境变量
+
+编辑 `.env.development` 和 `.env.production` 文件,设置API地址:
+
+```bash
+API_BASE_URL=http://localhost:3000
+API_VERSION=v1
+```
+
+### 3. 启动开发服务器
+
+```bash
+# 微信小程序
+npm run dev:weapp
+
+# H5
+npm run dev:h5
+```
+
+### 4. 构建生产版本
+
+```bash
+# 微信小程序
+npm run build:weapp
+
+# H5
+npm run build:h5
+```
+
+## API接口
+
+### 认证相关
+- POST `/api/v1/auth/login/password` - 密码登录
+- POST `/api/v1/auth/register` - 用户注册
+- GET `/api/v1/auth/me` - 获取当前用户信息
+- POST `/api/v1/auth/logout` - 退出登录
+
+### 用户相关
+- GET `/api/v1/users` - 获取用户列表
+- GET `/api/v1/users/:id` - 获取单个用户
+- PUT `/api/v1/users/:id` - 更新用户信息
+- DELETE `/api/v1/users/:id` - 删除用户
+
+## 使用说明
+
+### 认证状态管理
+
+项目提供了完整的认证状态管理工具:
+
+```typescript
+import { authManager } from '@/utils/auth'
+
+// 检查登录状态
+const isLoggedIn = authManager.isLoggedIn()
+
+// 获取用户信息
+const user = authManager.getUserInfo()
+
+// 获取token
+const token = authManager.getToken()
+
+// 登录
+const user = await authManager.login('username', 'password')
+
+// 注册
+const user = await authManager.register({
+  username: 'newuser',
+  password: 'password123',
+  email: 'user@example.com'
+})
+
+// 退出
+await authManager.logout()
+```
+
+### API调用
+
+使用封装的API客户端进行网络请求:
+
+```typescript
+import { authApi, userApi } from '@/utils/api'
+
+// 登录
+const response = await authApi.login({
+  username: 'username',
+  password: 'password'
+})
+
+// 获取用户列表
+const users = await userApi.getUsers()
+
+// 更新用户信息
+const updated = await userApi.updateUser(1, { username: 'newname' })
+```
+
+## 环境配置
+
+### 开发环境
+- API地址: `http://localhost:3000`
+- 环境变量文件: `.env.development`
+
+### 生产环境
+- API地址: `https://your-domain.com`
+- 环境变量文件: `.env.production`
+
+## 注意事项
+
+1. **CORS配置**: 确保后端API已配置CORS,允许小程序域名访问
+2. **HTTPS**: 生产环境必须使用HTTPS
+3. **域名配置**: 微信小程序需要在后台配置request合法域名
+4. **存储限制**: 小程序本地存储有大小限制,避免存储过多数据
+
+## 常见问题
+
+### 1. 网络请求失败
+- 检查API地址配置是否正确
+- 确保后端服务已启动
+- 检查网络连接
+
+### 2. 跨域问题
+- 在后端配置CORS
+- 使用代理服务器(开发环境)
+
+### 3. 登录状态丢失
+- 检查token是否正确存储
+- 确认token有效期
+
+## 扩展建议
+
+1. **添加微信登录**: 集成微信OAuth
+2. **手机号登录**: 添加短信验证码功能
+3. **第三方登录**: 支持QQ、微博等
+4. **用户头像**: 添加头像上传功能
+5. **用户权限**: 实现角色权限管理
+
+## 许可证
+
+MIT License

+ 6 - 4
mini/src/app.config.ts

@@ -1,11 +1,13 @@
 export default defineAppConfig({
   pages: [
-    'pages/index/index'
+    'pages/index/index',
+    'pages/login/index',
+    'pages/register/index'
   ],
   window: {
     backgroundTextStyle: 'light',
-    navigationBarBackgroundColor: '#fff',
-    navigationBarTitleText: 'WeChat',
-    navigationBarTextStyle: 'black'
+    navigationBarBackgroundColor: '#1890ff',
+    navigationBarTitleText: '小程序Starter',
+    navigationBarTextStyle: 'white'
   }
 })

+ 3 - 1
mini/src/pages/index/index.config.ts

@@ -1,3 +1,5 @@
 export default definePageConfig({
-  navigationBarTitleText: '首页'
+  navigationBarTitleText: '首页',
+  navigationBarBackgroundColor: '#1890ff',
+  navigationBarTextStyle: 'white'
 })

+ 129 - 0
mini/src/pages/index/index.css

@@ -0,0 +1,129 @@
+.index-container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding: 40rpx;
+}
+
+.user-welcome {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 60rpx 40rpx;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
+  text-align: center;
+}
+
+.user-avatar {
+  margin-bottom: 40rpx;
+}
+
+.avatar-image {
+  width: 200rpx;
+  height: 200rpx;
+  border-radius: 50%;
+  border: 4rpx solid #1890ff;
+}
+
+.welcome-text {
+  display: block;
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 40rpx;
+}
+
+.user-info {
+  margin-bottom: 60rpx;
+}
+
+.info-item {
+  display: block;
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 20rpx;
+}
+
+.action-buttons {
+  display: flex;
+  flex-direction: column;
+  gap: 30rpx;
+}
+
+.action-button {
+  width: 100%;
+  height: 88rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+  border-radius: 12rpx;
+}
+
+.login-prompt {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 80rpx 40rpx;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
+  text-align: center;
+}
+
+.welcome-section {
+  margin-bottom: 60rpx;
+}
+
+.welcome-title {
+  display: block;
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.welcome-subtitle {
+  display: block;
+  font-size: 32rpx;
+  color: #666;
+  margin-bottom: 40rpx;
+}
+
+.login-buttons {
+  display: flex;
+  flex-direction: column;
+  gap: 30rpx;
+  margin-bottom: 80rpx;
+}
+
+.login-button,
+.register-button {
+  width: 100%;
+  height: 88rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+  border-radius: 12rpx;
+}
+
+.register-button {
+  border: 2rpx solid #1890ff;
+  color: #1890ff;
+  background: transparent;
+}
+
+.features-section {
+  margin-top: 60rpx;
+}
+
+.features-title {
+  display: block;
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 30rpx;
+}
+
+.features-list {
+  text-align: left;
+}
+
+.feature-item {
+  display: block;
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 16rpx;
+}

+ 120 - 6
mini/src/pages/index/index.tsx

@@ -1,15 +1,129 @@
-import { View, Text } from '@tarojs/components'
-import { useLoad } from '@tarojs/taro'
+import { View, Text, Button, Image } from '@tarojs/components'
+import { useLoad, useDidShow } from '@tarojs/taro'
+import { useState } from 'react'
+import Taro from '@tarojs/taro'
+import { authManager, User } from '../../utils/auth'
 import './index.css'
 
-export default function Index () {
+export default function Index() {
+  const [user, setUser] = useState<User | null>(null)
+  const [loading, setLoading] = useState(false)
+
   useLoad(() => {
-    console.log('Page loaded.')
+    console.log('Index Page loaded.')
+    checkLoginStatus()
+  })
+
+  useDidShow(() => {
+    checkLoginStatus()
   })
 
+  const checkLoginStatus = async () => {
+    if (authManager.isLoggedIn()) {
+      const userInfo = authManager.getUserInfo()
+      setUser(userInfo)
+    } else {
+      setUser(null)
+    }
+  }
+
+  const handleLogin = () => {
+    Taro.navigateTo({ url: '/pages/login/index' })
+  }
+
+  const handleLogout = async () => {
+    setLoading(true)
+    try {
+      await authManager.logout()
+      setUser(null)
+      Taro.showToast({
+        title: '退出成功',
+        icon: 'success'
+      })
+    } catch (error) {
+      console.error('Logout error:', error)
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const handleProfile = () => {
+    Taro.navigateTo({ url: '/pages/profile/index' })
+  }
+
+  const goToRegister = () => {
+    Taro.navigateTo({ url: '/pages/register/index' })
+  }
+
   return (
-    <View className='index'>
-      <Text>Hello world!2222</Text>
+    <View className="index-container">
+      {user ? (
+        <View className="user-welcome">
+          <View className="user-avatar">
+            <Image
+              className="avatar-image"
+              src={user.avatar || 'https://via.placeholder.com/100x100'}
+              mode="aspectFill"
+            />
+          </View>
+          
+          <Text className="welcome-text">欢迎回来,{user.username}!</Text>
+          
+          <View className="user-info">
+            <Text className="info-item">ID: {user.id}</Text>
+            {user.email && <Text className="info-item">邮箱: {user.email}</Text>}
+            <Text className="info-item">注册时间: {new Date(user.createdAt).toLocaleDateString()}</Text>
+          </View>
+          
+          <View className="action-buttons">
+            <Button className="action-button" type="primary" onClick={handleProfile}>
+              查看资料
+            </Button>
+            <Button 
+              className="action-button" 
+              type="warn" 
+              loading={loading}
+              onClick={handleLogout}
+            >
+              退出登录
+            </Button>
+          </View>
+        </View>
+      ) : (
+        <View className="login-prompt">
+          <View className="welcome-section">
+            <Text className="welcome-title">欢迎使用小程序</Text>
+            <Text className="welcome-subtitle">请先登录以使用完整功能</Text>
+          </View>
+          
+          <View className="login-buttons">
+            <Button 
+              className="login-button" 
+              type="primary" 
+              onClick={handleLogin}
+            >
+              立即登录
+            </Button>
+            <Button 
+              className="register-button" 
+              plain
+              onClick={goToRegister}
+            >
+              注册新账号
+            </Button>
+          </View>
+          
+          <View className="features-section">
+            <Text className="features-title">功能特色</Text>
+            <View className="features-list">
+              <Text className="feature-item">✅ 安全的用户认证</Text>
+              <Text className="feature-item">✅ 完整的用户管理</Text>
+              <Text className="feature-item">✅ 响应式界面设计</Text>
+              <Text className="feature-item">✅ 实时数据同步</Text>
+            </View>
+          </View>
+        </View>
+      )}
     </View>
   )
 }

+ 5 - 0
mini/src/pages/login/index.config.ts

@@ -0,0 +1,5 @@
+export default definePageConfig({
+  navigationBarTitleText: '用户登录',
+  navigationBarBackgroundColor: '#1890ff',
+  navigationBarTextStyle: 'white'
+})

+ 88 - 0
mini/src/pages/login/index.css

@@ -0,0 +1,88 @@
+.login-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 40rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.login-header {
+  text-align: center;
+  margin-bottom: 80rpx;
+}
+
+.login-title {
+  display: block;
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 20rpx;
+}
+
+.login-subtitle {
+  display: block;
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+.login-form {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 60rpx 40rpx;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
+}
+
+.form-item {
+  margin-bottom: 40rpx;
+}
+
+.form-label {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 16rpx;
+  font-weight: 500;
+}
+
+.form-input {
+  width: 100%;
+  height: 88rpx;
+  border: 2rpx solid #e8e8e8;
+  border-radius: 12rpx;
+  padding: 0 24rpx;
+  font-size: 28rpx;
+  box-sizing: border-box;
+  background: #f8f8f8;
+}
+
+.form-input:focus {
+  border-color: #1890ff;
+  background: #fff;
+}
+
+.login-button {
+  width: 100%;
+  height: 88rpx;
+  background: #1890ff;
+  color: #fff;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-top: 40rpx;
+}
+
+.login-button:active {
+  background: #096dd9;
+}
+
+.login-footer {
+  text-align: center;
+  margin-top: 40rpx;
+}
+
+.register-link {
+  color: #1890ff;
+  font-size: 28rpx;
+  text-decoration: underline;
+}

+ 93 - 0
mini/src/pages/login/index.tsx

@@ -0,0 +1,93 @@
+import { View, Input, Button, Text } from '@tarojs/components'
+import { useState } from 'react'
+import Taro from '@tarojs/taro'
+import { authManager } from '../../utils/auth'
+import './index.css'
+
+export default function Login() {
+  const [username, setUsername] = useState('')
+  const [password, setPassword] = useState('')
+  const [loading, setLoading] = useState(false)
+
+  const handleLogin = async () => {
+    if (!username || !password) {
+      Taro.showToast({
+        title: '请输入用户名和密码',
+        icon: 'none'
+      })
+      return
+    }
+
+    setLoading(true)
+    try {
+      const user = await authManager.login(username, password)
+      Taro.showToast({
+        title: '登录成功',
+        icon: 'success'
+      })
+      
+      // 登录成功后跳转到首页
+      setTimeout(() => {
+        Taro.switchTab({ url: '/pages/index/index' })
+      }, 1500)
+    } catch (error: any) {
+      Taro.showToast({
+        title: error.message || '登录失败',
+        icon: 'none'
+      })
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const goToRegister = () => {
+    Taro.navigateTo({ url: '/pages/register/index' })
+  }
+
+  return (
+    <View className="login-container">
+      <View className="login-header">
+        <Text className="login-title">欢迎登录</Text>
+        <Text className="login-subtitle">请使用您的账号登录</Text>
+      </View>
+      
+      <View className="login-form">
+        <View className="form-item">
+          <Text className="form-label">用户名</Text>
+          <Input
+            className="form-input"
+            placeholder="请输入用户名"
+            value={username}
+            onInput={(e) => setUsername(e.detail.value)}
+          />
+        </View>
+        
+        <View className="form-item">
+          <Text className="form-label">密码</Text>
+          <Input
+            className="form-input"
+            placeholder="请输入密码"
+            password
+            value={password}
+            onInput={(e) => setPassword(e.detail.value)}
+          />
+        </View>
+        
+        <Button
+          className="login-button"
+          type="primary"
+          loading={loading}
+          onClick={handleLogin}
+        >
+          登录
+        </Button>
+        
+        <View className="login-footer">
+          <Text className="register-link" onClick={goToRegister}>
+            还没有账号?立即注册
+          </Text>
+        </View>
+      </View>
+    </View>
+  )
+}

+ 5 - 0
mini/src/pages/register/index.config.ts

@@ -0,0 +1,5 @@
+export default definePageConfig({
+  navigationBarTitleText: '用户注册',
+  navigationBarBackgroundColor: '#1890ff',
+  navigationBarTextStyle: 'white'
+})

+ 88 - 0
mini/src/pages/register/index.css

@@ -0,0 +1,88 @@
+.register-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 40rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.register-header {
+  text-align: center;
+  margin-bottom: 80rpx;
+}
+
+.register-title {
+  display: block;
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 20rpx;
+}
+
+.register-subtitle {
+  display: block;
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+.register-form {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 60rpx 40rpx;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
+}
+
+.form-item {
+  margin-bottom: 40rpx;
+}
+
+.form-label {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 16rpx;
+  font-weight: 500;
+}
+
+.form-input {
+  width: 100%;
+  height: 88rpx;
+  border: 2rpx solid #e8e8e8;
+  border-radius: 12rpx;
+  padding: 0 24rpx;
+  font-size: 28rpx;
+  box-sizing: border-box;
+  background: #f8f8f8;
+}
+
+.form-input:focus {
+  border-color: #1890ff;
+  background: #fff;
+}
+
+.register-button {
+  width: 100%;
+  height: 88rpx;
+  background: #1890ff;
+  color: #fff;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-top: 40rpx;
+}
+
+.register-button:active {
+  background: #096dd9;
+}
+
+.register-footer {
+  text-align: center;
+  margin-top: 40rpx;
+}
+
+.login-link {
+  color: #1890ff;
+  font-size: 28rpx;
+  text-decoration: underline;
+}

+ 138 - 0
mini/src/pages/register/index.tsx

@@ -0,0 +1,138 @@
+import { View, Input, Button, Text } from '@tarojs/components'
+import { useState } from 'react'
+import Taro from '@tarojs/taro'
+import { authManager } from '../../utils/auth'
+import './index.css'
+
+export default function Register() {
+  const [username, setUsername] = useState('')
+  const [email, setEmail] = useState('')
+  const [password, setPassword] = useState('')
+  const [confirmPassword, setConfirmPassword] = useState('')
+  const [loading, setLoading] = useState(false)
+
+  const handleRegister = async () => {
+    if (!username || !password) {
+      Taro.showToast({
+        title: '请输入用户名和密码',
+        icon: 'none'
+      })
+      return
+    }
+
+    if (password !== confirmPassword) {
+      Taro.showToast({
+        title: '两次输入的密码不一致',
+        icon: 'none'
+      })
+      return
+    }
+
+    if (password.length < 6) {
+      Taro.showToast({
+        title: '密码长度不能少于6位',
+        icon: 'none'
+      })
+      return
+    }
+
+    setLoading(true)
+    try {
+      const user = await authManager.register({
+        username,
+        password,
+        email: email || undefined
+      })
+      
+      Taro.showToast({
+        title: '注册成功',
+        icon: 'success'
+      })
+      
+      // 注册成功后跳转到首页
+      setTimeout(() => {
+        Taro.switchTab({ url: '/pages/index/index' })
+      }, 1500)
+    } catch (error: any) {
+      Taro.showToast({
+        title: error.message || '注册失败',
+        icon: 'none'
+      })
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const goToLogin = () => {
+    Taro.navigateBack()
+  }
+
+  return (
+    <View className="register-container">
+      <View className="register-header">
+        <Text className="register-title">创建账号</Text>
+        <Text className="register-subtitle">欢迎来到我们的小程序</Text>
+      </View>
+      
+      <View className="register-form">
+        <View className="form-item">
+          <Text className="form-label">用户名</Text>
+          <Input
+            className="form-input"
+            placeholder="请输入用户名"
+            value={username}
+            onInput={(e) => setUsername(e.detail.value)}
+          />
+        </View>
+        
+        <View className="form-item">
+          <Text className="form-label">邮箱(可选)</Text>
+          <Input
+            className="form-input"
+            placeholder="请输入邮箱地址"
+            type="text"
+            value={email}
+            onInput={(e) => setEmail(e.detail.value)}
+          />
+        </View>
+        
+        <View className="form-item">
+          <Text className="form-label">密码</Text>
+          <Input
+            className="form-input"
+            placeholder="请输入密码(至少6位)"
+            password
+            value={password}
+            onInput={(e) => setPassword(e.detail.value)}
+          />
+        </View>
+        
+        <View className="form-item">
+          <Text className="form-label">确认密码</Text>
+          <Input
+            className="form-input"
+            placeholder="请再次输入密码"
+            password
+            value={confirmPassword}
+            onInput={(e) => setConfirmPassword(e.detail.value)}
+          />
+        </View>
+        
+        <Button
+          className="register-button"
+          type="primary"
+          loading={loading}
+          onClick={handleRegister}
+        >
+          注册
+        </Button>
+        
+        <View className="register-footer">
+          <Text className="login-link" onClick={goToLogin}>
+            已有账号?立即登录
+          </Text>
+        </View>
+      </View>
+    </View>
+  )
+}

+ 149 - 0
mini/src/utils/api.ts

@@ -0,0 +1,149 @@
+import Taro from '@tarojs/taro'
+
+// API配置
+const API_BASE_URL = process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'
+const API_VERSION = process.env.TARO_APP_API_VERSION || 'v1'
+
+// 完整的API地址
+const BASE_URL = `${API_BASE_URL}/api/${API_VERSION}`
+
+// 请求拦截器
+const requestInterceptor = (options: Taro.request.Option) => {
+  // 添加token
+  const token = Taro.getStorageSync('token')
+  if (token) {
+    options.header = {
+      ...options.header,
+      'Authorization': `Bearer ${token}`
+    }
+  }
+  
+  // 设置基础URL
+  options.url = `${BASE_URL}${options.url}`
+  
+  // 设置默认header
+  options.header = {
+    'Content-Type': 'application/json',
+    ...options.header
+  }
+  
+  return options
+}
+
+// 响应拦截器
+const responseInterceptor = (response: Taro.request.SuccessCallbackResult<any>) => {
+  const { statusCode, data } = response
+  
+  if (statusCode === 200 || statusCode === 201) {
+    // 检查数据结构,支持后端返回的格式
+    if (data && data.data) {
+      return data.data
+    }
+    return data
+  } else if (statusCode === 401) {
+    // 未授权,清除token并跳转到登录页
+    Taro.removeStorageSync('token')
+    Taro.removeStorageSync('userInfo')
+    Taro.navigateTo({ url: '/pages/login/index' })
+    throw new Error('请重新登录')
+  } else {
+    throw new Error(data.message || '请求失败')
+  }
+}
+
+// 错误处理
+const errorHandler = (error: any) => {
+  console.error('API Error:', error)
+  Taro.showToast({
+    title: error.message || '网络错误',
+    icon: 'none'
+  })
+  throw error
+}
+
+// 封装请求方法
+class ApiClient {
+  async request(options: Taro.request.Option) {
+    try {
+      const finalOptions = requestInterceptor(options)
+      const response = await Taro.request(finalOptions)
+      return responseInterceptor(response)
+    } catch (error) {
+      errorHandler(error)
+    }
+  }
+
+  async get(url: string, params?: any) {
+    return this.request({
+      url,
+      method: 'GET',
+      data: params
+    })
+  }
+
+  async post(url: string, data?: any) {
+    return this.request({
+      url,
+      method: 'POST',
+      data
+    })
+  }
+
+  async put(url: string, data?: any) {
+    return this.request({
+      url,
+      method: 'PUT',
+      data
+    })
+  }
+
+  async delete(url: string) {
+    return this.request({
+      url,
+      method: 'DELETE'
+    })
+  }
+}
+
+// 创建单例实例
+export const apiClient = new ApiClient()
+
+// 认证相关API
+export const authApi = {
+  // 密码登录
+  login: (data: { username: string; password: string }) => 
+    apiClient.post('/auth/login', data),
+  
+  // 用户注册
+  register: (data: { username: string; password: string; email?: string }) => 
+    apiClient.post('/auth/register', data),
+  
+  // 获取当前用户信息
+  getCurrentUser: () => 
+    apiClient.get('/auth/me'),
+  
+  // 退出登录
+  logout: () => 
+    apiClient.post('/auth/logout')
+}
+
+// 用户相关API
+export const userApi = {
+  // 获取用户列表
+  getUsers: (params?: any) => 
+    apiClient.get('/users', params),
+  
+  // 获取单个用户信息
+  getUser: (id: number) => 
+    apiClient.get(`/users/${id}`),
+  
+  // 更新用户信息
+  updateUser: (id: number, data: any) => 
+    apiClient.put(`/users/${id}`, data),
+  
+  // 删除用户
+  deleteUser: (id: number) => 
+    apiClient.delete(`/users/${id}`)
+}
+
+export default apiClient

+ 127 - 0
mini/src/utils/auth.ts

@@ -0,0 +1,127 @@
+import Taro from '@tarojs/taro'
+import { authApi } from './api'
+
+// 用户类型定义
+export interface User {
+  id: number
+  username: string
+  email?: string
+  avatar?: string
+  createdAt: string
+  updatedAt: string
+}
+
+// 认证状态管理
+class AuthManager {
+  private static instance: AuthManager
+  
+  static getInstance(): AuthManager {
+    if (!AuthManager.instance) {
+      AuthManager.instance = new AuthManager()
+    }
+    return AuthManager.instance
+  }
+
+  // 获取token
+  getToken(): string | null {
+    return Taro.getStorageSync('token')
+  }
+
+  // 设置token
+  setToken(token: string): void {
+    Taro.setStorageSync('token', token)
+  }
+
+  // 清除token
+  removeToken(): void {
+    Taro.removeStorageSync('token')
+  }
+
+  // 获取用户信息
+  getUserInfo(): User | null {
+    const userInfo = Taro.getStorageSync('userInfo')
+    return userInfo ? JSON.parse(userInfo) : null
+  }
+
+  // 设置用户信息
+  setUserInfo(user: User): void {
+    Taro.setStorageSync('userInfo', JSON.stringify(user))
+  }
+
+  // 清除用户信息
+  removeUserInfo(): void {
+    Taro.removeStorageSync('userInfo')
+  }
+
+  // 检查是否已登录
+  isLoggedIn(): boolean {
+    return !!this.getToken()
+  }
+
+  // 登录
+  async login(username: string, password: string): Promise<User> {
+    try {
+      const response = await authApi.login({ username, password })
+      const { token, user } = response
+      
+      this.setToken(token)
+      this.setUserInfo(user)
+      
+      return user
+    } catch (error) {
+      throw new Error('登录失败,请检查用户名和密码')
+    }
+  }
+
+  // 注册
+  async register(data: {
+    username: string
+    password: string
+    email?: string
+  }): Promise<User> {
+    try {
+      const response = await authApi.register(data)
+      const { token, user } = response
+      
+      this.setToken(token)
+      this.setUserInfo(user)
+      
+      return user
+    } catch (error) {
+      throw new Error('注册失败,请重试')
+    }
+  }
+
+  // 登出
+  async logout(): Promise<void> {
+    try {
+      await authApi.logout()
+    } catch (error) {
+      console.error('Logout error:', error)
+    } finally {
+      this.removeToken()
+      this.removeUserInfo()
+      Taro.redirectTo({ url: '/pages/login/index' })
+    }
+  }
+
+  // 获取当前用户信息
+  async getCurrentUser(): Promise<User | null> {
+    if (!this.isLoggedIn()) {
+      return null
+    }
+
+    try {
+      const user = await authApi.getCurrentUser()
+      this.setUserInfo(user)
+      return user
+    } catch (error) {
+      this.removeToken()
+      this.removeUserInfo()
+      return null
+    }
+  }
+}
+
+// 导出单例实例
+export const authManager = AuthManager.getInstance()