uni-app 用户管理

核心知识点

1. 用户系统架构

一个完整的用户管理系统通常包括以下几个核心部分:

  • 用户认证模块:负责用户注册、登录、注销等功能
  • 用户信息模块:负责用户个人信息的管理和更新
  • 权限管理模块:负责用户权限的分配和控制
  • 用户状态管理:负责用户登录状态的维护和管理
  • 安全保障模块:负责用户数据的安全和隐私保护

2. 用户认证方式

在 uni-app 中,我们可以实现多种用户认证方式:

  • 账号密码登录:传统的用户名/邮箱/手机号 + 密码登录
  • 短信验证码登录:通过手机号接收验证码进行登录
  • 第三方登录:集成微信、支付宝、QQ等第三方平台登录
  • 生物识别登录:集成指纹、面容识别等生物特征登录
  • Token 登录:使用 JWT 等 Token 进行无状态登录

3. 用户注册流程

一个完整的用户注册流程通常包括以下步骤:

  1. 用户信息收集:收集用户名、密码、邮箱、手机号等信息
  2. 信息验证:验证用户输入的信息是否符合要求
  3. 短信/邮箱验证:通过短信或邮箱验证码验证用户身份
  4. 密码加密:对用户密码进行加密存储
  5. 用户创建:在数据库中创建用户记录
  6. 注册成功:返回注册成功信息,引导用户登录

4. 用户登录流程

用户登录流程通常包括以下步骤:

  1. 用户身份验证:验证用户输入的账号和密码是否正确
  2. Token 生成:生成用户登录凭证(如 JWT Token)
  3. 登录状态保存:将登录状态保存到本地存储
  4. 用户信息获取:获取用户详细信息
  5. 登录成功:返回登录成功信息,跳转到首页

5. 权限管理体系

权限管理是用户系统的重要组成部分,通常包括以下几个层次:

  • 用户角色:定义不同用户的角色,如管理员、普通用户、VIP用户等
  • 权限控制:为不同角色分配不同的权限
  • 资源访问控制:控制用户对不同资源的访问权限
  • 操作权限控制:控制用户对不同操作的执行权限

实用案例

案例:实现一个完整的用户管理系统

1. 项目结构

src/
  ├── components/
  │   ├── login-form.vue       # 登录表单组件
  │   ├── register-form.vue    # 注册表单组件
  │   └── user-info.vue         # 用户信息组件
  ├── pages/
  │   ├── auth/
  │   │   ├── login.vue         # 登录页面
  │   │   ├── register.vue      # 注册页面
  │   │   └── forgot-password.vue # 忘记密码页面
  │   └── user/
  │       ├── index.vue         # 用户中心页面
  │       ├── profile.vue       # 个人资料页面
  │       └── settings.vue      # 设置页面
  ├── services/
  │   ├── auth-api.js           # 认证API服务
  │   └── user-api.js           # 用户API服务
  ├── utils/
  │   ├── auth.js               # 认证工具函数
  │   └── permission.js         # 权限管理工具函数
  └── store/
      └── modules/
          └── user.js           # 用户状态管理

2. 用户数据模型

// 用户数据模型
const userModel = {
  id: String,           // 用户ID
  username: String,     // 用户名
  password: String,     // 密码(加密存储)
  email: String,        // 邮箱
  phone: String,        // 手机号
  avatar: String,       // 头像URL
  nickname: String,     // 昵称
  gender: Number,       // 性别:0-未知,1-男,2-女
  birthday: Date,       // 生日
  address: String,      // 地址
  role: String,         // 角色:admin, user, vip
  status: String,       // 状态:active, inactive, banned
  createTime: Date,     // 创建时间
  lastLoginTime: Date,  // 最后登录时间
  lastLoginIp: String,  // 最后登录IP
  permissions: Array,   // 权限列表
  settings: Object      // 用户设置
};

3. 认证API服务

// services/auth-api.js

// 用户登录
export const login = async (params) => {
  const { username, password, type = 'password' } = params;
  try {
    const res = await uni.request({
      url: 'https://api.example.com/auth/login',
      method: 'POST',
      data: {
        username,
        password,
        type
      }
    });
    
    if (res.data.code === 0) {
      // 保存登录凭证
      uni.setStorageSync('token', res.data.data.token);
      uni.setStorageSync('userInfo', res.data.data.user);
    }
    
    return res.data;
  } catch (error) {
    console.error('登录失败:', error);
    throw error;
  }
};

// 用户注册
export const register = async (params) => {
  const { username, password, email, phone, code } = params;
  try {
    const res = await uni.request({
      url: 'https://api.example.com/auth/register',
      method: 'POST',
      data: {
        username,
        password,
        email,
        phone,
        code
      }
    });
    return res.data;
  } catch (error) {
    console.error('注册失败:', error);
    throw error;
  }
};

// 发送验证码
export const sendCode = async (params) => {
  const { phone, type = 'register' } = params;
  try {
    const res = await uni.request({
      url: 'https://api.example.com/auth/send-code',
      method: 'POST',
      data: {
        phone,
        type
      }
    });
    return res.data;
  } catch (error) {
    console.error('发送验证码失败:', error);
    throw error;
  }
};

// 忘记密码
export const forgotPassword = async (params) => {
  const { phone, code, password } = params;
  try {
    const res = await uni.request({
      url: 'https://api.example.com/auth/forgot-password',
      method: 'POST',
      data: {
        phone,
        code,
        password
      }
    });
    return res.data;
  } catch (error) {
    console.error('重置密码失败:', error);
    throw error;
  }
};

// 用户注销
export const logout = async () => {
  try {
    // 清除本地存储的登录凭证
    uni.removeStorageSync('token');
    uni.removeStorageSync('userInfo');
    
    // 调用后端注销接口
    const res = await uni.request({
      url: 'https://api.example.com/auth/logout',
      method: 'POST',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      }
    });
    
    return res.data;
  } catch (error) {
    console.error('注销失败:', error);
    // 即使后端注销失败,也清除本地存储
    uni.removeStorageSync('token');
    uni.removeStorageSync('userInfo');
    throw error;
  }
};

// 刷新Token
export const refreshToken = async () => {
  try {
    const res = await uni.request({
      url: 'https://api.example.com/auth/refresh-token',
      method: 'POST',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      }
    });
    
    if (res.data.code === 0) {
      uni.setStorageSync('token', res.data.data.token);
    }
    
    return res.data;
  } catch (error) {
    console.error('刷新Token失败:', error);
    throw error;
  }
};

4. 用户API服务

// services/user-api.js

// 获取用户信息
export const getUserInfo = async () => {
  try {
    const res = await uni.request({
      url: 'https://api.example.com/user/info',
      method: 'GET',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      }
    });
    return res.data;
  } catch (error) {
    console.error('获取用户信息失败:', error);
    throw error;
  }
};

// 更新用户信息
export const updateUserInfo = async (data) => {
  try {
    const res = await uni.request({
      url: 'https://api.example.com/user/info',
      method: 'PUT',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      },
      data
    });
    return res.data;
  } catch (error) {
    console.error('更新用户信息失败:', error);
    throw error;
  }
};

// 更新用户密码
export const updatePassword = async (data) => {
  const { oldPassword, newPassword } = data;
  try {
    const res = await uni.request({
      url: 'https://api.example.com/user/password',
      method: 'PUT',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      },
      data: {
        oldPassword,
        newPassword
      }
    });
    return res.data;
  } catch (error) {
    console.error('更新密码失败:', error);
    throw error;
  }
};

// 上传用户头像
export const uploadAvatar = async (filePath) => {
  try {
    const res = await uni.uploadFile({
      url: 'https://api.example.com/user/avatar',
      filePath,
      name: 'avatar',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      }
    });
    
    return JSON.parse(res.data);
  } catch (error) {
    console.error('上传头像失败:', error);
    throw error;
  }
};

// 获取用户列表(管理员权限)
export const getUserList = async (params) => {
  const { page = 1, pageSize = 10, role, status } = params;
  try {
    const res = await uni.request({
      url: 'https://api.example.com/user/list',
      method: 'GET',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      },
      data: {
        page,
        pageSize,
        role,
        status
      }
    });
    return res.data;
  } catch (error) {
    console.error('获取用户列表失败:', error);
    throw error;
  }
};

// 更新用户状态(管理员权限)
export const updateUserStatus = async (userId, status) => {
  try {
    const res = await uni.request({
      url: `https://api.example.com/user/${userId}/status`,
      method: 'PUT',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      },
      data: {
        status
      }
    });
    return res.data;
  } catch (error) {
    console.error('更新用户状态失败:', error);
    throw error;
  }
};

// 更新用户角色(管理员权限)
export const updateUserRole = async (userId, role) => {
  try {
    const res = await uni.request({
      url: `https://api.example.com/user/${userId}/role`,
      method: 'PUT',
      header: {
        'Authorization': `Bearer ${uni.getStorageSync('token')}`
      },
      data: {
        role
      }
    });
    return res.data;
  } catch (error) {
    console.error('更新用户角色失败:', error);
    throw error;
  }
};

5. 认证工具函数

// utils/auth.js

// 检查用户是否已登录
export const isLoggedIn = () => {
  const token = uni.getStorageSync('token');
  return !!token;
};

// 获取当前用户信息
export const getCurrentUser = () => {
  return uni.getStorageSync('userInfo') || null;
};

// 获取认证Token
export const getToken = () => {
  return uni.getStorageSync('token') || '';
};

// 设置用户信息
export const setUserInfo = (userInfo) => {
  uni.setStorageSync('userInfo', userInfo);
};

// 清除用户信息
export const clearUserInfo = () => {
  uni.removeStorageSync('token');
  uni.removeStorageSync('userInfo');
};

// 检查Token是否即将过期(例如:剩余时间小于10分钟)
export const isTokenExpiring = (token) => {
  if (!token) return true;
  
  try {
    const payload = JSON.parse(atob(token.split('.')[1]));
    const exp = payload.exp * 1000;
    const now = Date.now();
    const timeLeft = exp - now;
    return timeLeft < 10 * 60 * 1000; // 10分钟
  } catch (error) {
    return true;
  }
};

// 生成认证请求头
export const getAuthHeader = () => {
  const token = getToken();
  return token ? { 'Authorization': `Bearer ${token}` } : {};
};

6. 权限管理工具函数

// utils/permission.js

// 检查用户是否拥有指定权限
export const hasPermission = (permission) => {
  const userInfo = uni.getStorageSync('userInfo');
  if (!userInfo || !userInfo.permissions) {
    return false;
  }
  
  return userInfo.permissions.includes(permission);
};

// 检查用户是否属于指定角色
export const hasRole = (role) => {
  const userInfo = uni.getStorageSync('userInfo');
  if (!userInfo || !userInfo.role) {
    return false;
  }
  
  if (Array.isArray(role)) {
    return role.includes(userInfo.role);
  }
  
  return userInfo.role === role;
};

// 检查用户是否是管理员
export const isAdmin = () => {
  return hasRole('admin');
};

// 检查用户是否是VIP
export const isVip = () => {
  return hasRole('vip');
};

// 权限验证装饰器
export const requirePermission = (permission) => {
  return (to, from, next) => {
    if (hasPermission(permission)) {
      next();
    } else {
      uni.showToast({ title: '无权限访问', icon: 'none' });
      next({ path: '/pages/user/index' });
    }
  };
};

// 角色验证装饰器
export const requireRole = (role) => {
  return (to, from, next) => {
    if (hasRole(role)) {
      next();
    } else {
      uni.showToast({ title: '无权限访问', icon: 'none' });
      next({ path: '/pages/user/index' });
    }
  };
};

7. 用户状态管理

// store/modules/user.js
import { getUserInfo as fetchUserInfo } from '@/services/user-api';
import { isLoggedIn, getCurrentUser, setUserInfo, clearUserInfo } from '@/utils/auth';

const state = {
  userInfo: getCurrentUser(),
  isLogin: isLoggedIn(),
  loading: false,
  error: null
};

const mutations = {
  SET_USER_INFO(state, userInfo) {
    state.userInfo = userInfo;
    state.isLogin = true;
    setUserInfo(userInfo);
  },
  SET_LOADING(state, loading) {
    state.loading = loading;
  },
  SET_ERROR(state, error) {
    state.error = error;
  },
  LOGOUT(state) {
    state.userInfo = null;
    state.isLogin = false;
    clearUserInfo();
  }
};

const actions = {
  async getUserInfo({ commit }) {
    commit('SET_LOADING', true);
    commit('SET_ERROR', null);
    
    try {
      const result = await fetchUserInfo();
      if (result.code === 0) {
        commit('SET_USER_INFO', result.data);
      } else {
        commit('SET_ERROR', result.message);
      }
    } catch (error) {
      commit('SET_ERROR', error.message);
    } finally {
      commit('SET_LOADING', false);
    }
  },
  
  logout({ commit }) {
    commit('LOGOUT');
  },
  
  updateUserInfo({ commit }, userInfo) {
    commit('SET_USER_INFO', userInfo);
  }
};

const getters = {
  userInfo: state => state.userInfo,
  isLogin: state => state.isLogin,
  loading: state => state.loading,
  error: state => state.error,
  userId: state => state.userInfo?.id,
  username: state => state.userInfo?.username,
  nickname: state => state.userInfo?.nickname,
  avatar: state => state.userInfo?.avatar,
  role: state => state.userInfo?.role,
  permissions: state => state.userInfo?.permissions || []
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};

8. 登录表单组件

<!-- components/login-form.vue -->
<template>
  <view class="login-form">
    <view class="form-item">
      <text class="label">账号</text>
      <input 
        v-model="loginForm.username" 
        class="input" 
        placeholder="请输入手机号/邮箱/用户名"
        @keyup.enter="handleLogin"
      />
    </view>
    
    <view class="form-item">
      <text class="label">密码</text>
      <view class="password-input">
        <input 
          v-model="loginForm.password" 
          class="input" 
          type="password" 
          placeholder="请输入密码"
          @keyup.enter="handleLogin"
        />
        <text 
          class="password-toggle" 
          @click="togglePasswordVisibility"
        >
          {{ showPassword ? '隐藏' : '显示' }}
        </text>
      </view>
    </view>
    
    <view class="form-options">
      <checkbox-group v-model="loginForm.remember">
        <label class="checkbox-label">
          <checkbox value="true" />
          <text>记住密码</text>
        </label>
      </checkbox-group>
      <navigator url="/pages/auth/forgot-password" class="forgot-password">
        忘记密码?
      </navigator>
    </view>
    
    <button 
      class="login-btn" 
      :loading="loading" 
      :disabled="loading" 
      @click="handleLogin"
    >
      登录
    </button>
    
    <view class="other-login">
      <text class="divider">其他登录方式</text>
      <view class="login-icons">
        <text class="icon wechat" @click="loginWithWechat">微信</text>
        <text class="icon alipay" @click="loginWithAlipay">支付宝</text>
        <text class="icon qq" @click="loginWithQQ">QQ</text>
      </view>
    </view>
    
    <view class="register-link">
      还没有账号?
      <navigator url="/pages/auth/register" class="register-btn">立即注册</navigator>
    </view>
  </view>
</template>

<script>
import { login } from '@/services/auth-api';
import { mapActions } from 'vuex';

export default {
  name: 'LoginForm',
  data() {
    return {
      loginForm: {
        username: '',
        password: '',
        remember: []
      },
      showPassword: false,
      loading: false
    };
  },
  methods: {
    ...mapActions('user', ['getUserInfo']),
    
    togglePasswordVisibility() {
      this.showPassword = !this.showPassword;
    },
    
    async handleLogin() {
      if (!this.loginForm.username || !this.loginForm.password) {
        uni.showToast({ title: '请输入账号和密码', icon: 'none' });
        return;
      }
      
      this.loading = true;
      try {
        const result = await login(this.loginForm);
        if (result.code === 0) {
          // 登录成功,获取用户信息
          await this.getUserInfo();
          uni.showToast({ title: '登录成功' });
          // 跳转到首页或之前的页面
          uni.navigateBack({ delta: 1 });
        } else {
          uni.showToast({ title: result.message, icon: 'none' });
        }
      } catch (error) {
        uni.showToast({ title: '登录失败,请重试', icon: 'none' });
      } finally {
        this.loading = false;
      }
    },
    
    async loginWithWechat() {
      try {
        const [error, res] = await uni.login({
          provider: 'weixin'
        });
        
        if (error) {
          uni.showToast({ title: '微信登录失败', icon: 'none' });
          return;
        }
        
        // 调用后端微信登录接口
        const result = await login({
          username: res.code,
          password: 'wechat',
          type: 'wechat'
        });
        
        if (result.code === 0) {
          await this.getUserInfo();
          uni.showToast({ title: '登录成功' });
          uni.navigateBack({ delta: 1 });
        } else {
          uni.showToast({ title: result.message, icon: 'none' });
        }
      } catch (error) {
        uni.showToast({ title: '微信登录失败', icon: 'none' });
      }
    },
    
    async loginWithAlipay() {
      // 支付宝登录实现
      uni.showToast({ title: '支付宝登录功能开发中', icon: 'none' });
    },
    
    async loginWithQQ() {
      // QQ登录实现
      uni.showToast({ title: 'QQ登录功能开发中', icon: 'none' });
    }
  }
};
</script>

<style scoped>
.login-form {
  padding: 32rpx;
}

.form-item {
  margin-bottom: 32rpx;
}

.label {
  display: block;
  margin-bottom: 8rpx;
  font-size: 28rpx;
  font-weight: bold;
}

.input {
  width: 100%;
  padding: 16rpx;
  border: 1rpx solid #ddd;
  border-radius: 8rpx;
  font-size: 28rpx;
  box-sizing: border-box;
}

.password-input {
  position: relative;
}

.password-toggle {
  position: absolute;
  right: 16rpx;
  top: 50%;
  transform: translateY(-50%);
  color: #007AFF;
  font-size: 24rpx;
}

.form-options {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 32rpx;
}

.checkbox-label {
  display: flex;
  align-items: center;
  font-size: 24rpx;
}

.forgot-password {
  font-size: 24rpx;
  color: #007AFF;
}

.login-btn {
  width: 100%;
  padding: 16rpx;
  background-color: #007AFF;
  color: white;
  border-radius: 8rpx;
  font-size: 28rpx;
  margin-bottom: 32rpx;
}

.other-login {
  margin-bottom: 32rpx;
}

.divider {
  display: block;
  text-align: center;
  color: #999;
  font-size: 24rpx;
  margin-bottom: 32rpx;
  position: relative;
}

.divider::before,
.divider::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 30%;
  height: 1rpx;
  background-color: #ddd;
}

.divider::before {
  left: 0;
}

.divider::after {
  right: 0;
}

.login-icons {
  display: flex;
  justify-content: center;
}

.icon {
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 24rpx;
  font-size: 24rpx;
  color: white;
}

.icon.wechat {
  background-color: #07C160;
}

.icon.alipay {
  background-color: #1677FF;
}

.icon.qq {
  background-color: #12B7F5;
}

.register-link {
  text-align: center;
  font-size: 24rpx;
  color: #666;
}

.register-btn {
  color: #007AFF;
  margin-left: 8rpx;
}
</style>

9. 注册表单组件

<!-- components/register-form.vue -->
<template>
  <view class="register-form">
    <view class="form-item">
      <text class="label">手机号</text>
      <input 
        v-model="registerForm.phone" 
        class="input" 
        placeholder="请输入手机号"
        type="number"
      />
    </view>
    
    <view class="form-item">
      <text class="label">验证码</text>
      <view class="code-input">
        <input 
          v-model="registerForm.code" 
          class="input" 
          placeholder="请输入验证码"
          type="number"
        />
        <button 
          class="code-btn" 
          :disabled="countingDown" 
          @click="sendCode"
        >
          {{ countingDown ? `${countDown}s后重发` : '获取验证码' }}
        </button>
      </view>
    </view>
    
    <view class="form-item">
      <text class="label">密码</text>
      <input 
        v-model="registerForm.password" 
        class="input" 
        placeholder="请设置密码(6-20位)"
        type="password"
      />
    </view>
    
    <view class="form-item">
      <text class="label">确认密码</text>
      <input 
        v-model="registerForm.confirmPassword" 
        class="input" 
        placeholder="请再次输入密码"
        type="password"
      />
    </view>
    
    <view class="form-item">
      <text class="label">用户名</text>
      <input 
        v-model="registerForm.username" 
        class="input" 
        placeholder="请设置用户名"
      />
    </view>
    
    <checkbox-group v-model="registerForm.agreement">
      <label class="agreement-label">
        <checkbox value="true" />
        <text>
          我已阅读并同意
          <text class="agreement-link">《用户协议》</text>
          和
          <text class="agreement-link">《隐私政策》</text>
        </text>
      </label>
    </checkbox-group>
    
    <button 
      class="register-btn" 
      :loading="loading" 
      :disabled="loading" 
      @click="handleRegister"
    >
      注册
    </button>
    
    <view class="login-link">
      已有账号?
      <navigator url="/pages/auth/login" class="login-btn">立即登录</navigator>
    </view>
  </view>
</template>

<script>
import { register, sendCode as sendVerificationCode } from '@/services/auth-api';

export default {
  name: 'RegisterForm',
  data() {
    return {
      registerForm: {
        phone: '',
        code: '',
        password: '',
        confirmPassword: '',
        username: '',
        agreement: []
      },
      loading: false,
      countingDown: false,
      countDown: 60
    };
  },
  methods: {
    async sendCode() {
      if (!this.registerForm.phone) {
        uni.showToast({ title: '请输入手机号', icon: 'none' });
        return;
      }
      
      if (!/^1[3-9]\d{9}$/.test(this.registerForm.phone)) {
        uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
        return;
      }
      
      try {
        const result = await sendVerificationCode({ phone: this.registerForm.phone });
        if (result.code === 0) {
          uni.showToast({ title: '验证码发送成功', icon: 'success' });
          this.startCountDown();
        } else {
          uni.showToast({ title: result.message, icon: 'none' });
        }
      } catch (error) {
        uni.showToast({ title: '验证码发送失败', icon: 'none' });
      }
    },
    
    startCountDown() {
      this.countingDown = true;
      this.countDown = 60;
      
      const timer = setInterval(() => {
        this.countDown--;
        if (this.countDown <= 0) {
          clearInterval(timer);
          this.countingDown = false;
        }
      }, 1000);
    },
    
    async handleRegister() {
      // 表单验证
      if (!this.registerForm.phone) {
        uni.showToast({ title: '请输入手机号', icon: 'none' });
        return;
      }
      
      if (!this.registerForm.code) {
        uni.showToast({ title: '请输入验证码', icon: 'none' });
        return;
      }
      
      if (!this.registerForm.password) {
        uni.showToast({ title: '请设置密码', icon: 'none' });
        return;
      }
      
      if (this.registerForm.password.length < 6 || this.registerForm.password.length > 20) {
        uni.showToast({ title: '密码长度应在6-20位之间', icon: 'none' });
        return;
      }
      
      if (this.registerForm.password !== this.registerForm.confirmPassword) {
        uni.showToast({ title: '两次输入的密码不一致', icon: 'none' });
        return;
      }
      
      if (!this.registerForm.username) {
        uni.showToast({ title: '请设置用户名', icon: 'none' });
        return;
      }
      
      if (!this.registerForm.agreement.includes('true')) {
        uni.showToast({ title: '请阅读并同意用户协议和隐私政策', icon: 'none' });
        return;
      }
      
      this.loading = true;
      try {
        const result = await register(this.registerForm);
        if (result.code === 0) {
          uni.showToast({ title: '注册成功', icon: 'success' });
          // 注册成功后跳转到登录页
          setTimeout(() => {
            uni.navigateTo({
              url: '/pages/auth/login'
            });
          }, 1000);
        } else {
          uni.showToast({ title: result.message, icon: 'none' });
        }
      } catch (error) {
        uni.showToast({ title: '注册失败,请重试', icon: 'none' });
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

<style scoped>
.register-form {
  padding: 32rpx;
}

.form-item {
  margin-bottom: 32rpx;
}

.label {
  display: block;
  margin-bottom: 8rpx;
  font-size: 28rpx;
  font-weight: bold;
}

.input {
  width: 100%;
  padding: 16rpx;
  border: 1rpx solid #ddd;
  border-radius: 8rpx;
  font-size: 28rpx;
  box-sizing: border-box;
}

.code-input {
  display: flex;
  align-items: center;
}

.code-input .input {
  flex: 1;
  margin-right: 16rpx;
}

.code-btn {
  width: 200rpx;
  padding: 16rpx;
  border-radius: 8rpx;
  background-color: #f5f5f5;
  color: #007AFF;
  font-size: 24rpx;
}

.agreement-label {
  display: flex;
  align-items: flex-start;
  font-size: 24rpx;
  color: #666;
  margin-bottom: 32rpx;
}

.agreement-link {
  color: #007AFF;
}

.register-btn {
  width: 100%;
  padding: 16rpx;
  background-color: #007AFF;
  color: white;
  border-radius: 8rpx;
  font-size: 28rpx;
  margin-bottom: 32rpx;
}

.login-link {
  text-align: center;
  font-size: 24rpx;
  color: #666;
}

.login-btn {
  color: #007AFF;
  margin-left: 8rpx;
}
</style>

10. 用户中心页面

<!-- pages/user/index.vue -->
<template>
  <view class="user-center">
    <view v-if="isLogin" class="user-info">
      <image 
        :src="userInfo.avatar || '/static/default-avatar.png'" 
        class="avatar"
      />
      <view class="user-details">
        <text class="nickname">{{ userInfo.nickname || userInfo.username }}</text>
        <text class="user-id">ID: {{ userInfo.id }}</text>
      </view>
      <navigator url="/pages/user/profile" class="edit-btn">编辑资料</navigator>
    </view>
    
    <view v-else class="login-prompt">
      <image src="/static/default-avatar.png" class="avatar" />
      <text class="prompt-text">登录后查看个人信息</text>
      <navigator url="/pages/auth/login" class="login-btn">立即登录</navigator>
    </view>
    
    <view class="menu-list">
      <navigator url="/pages/user/orders" class="menu-item">
        <text class="menu-icon">📋</text>
        <text class="menu-text">我的订单</text>
        <text class="menu-arrow">></text>
      </navigator>
      
      <navigator url="/pages/user/addresses" class="menu-item">
        <text class="menu-icon">📍</text>
        <text class="menu-text">收货地址</text>
        <text class="menu-arrow">></text>
      </navigator>
      
      <navigator url="/pages/user/favorites" class="menu-item">
        <text class="menu-icon">❤️</text>
        <text class="menu-text">我的收藏</text>
        <text class="menu-arrow">></text>
      </navigator>
      
      <navigator url="/pages/user/coupons" class="menu-item">
        <text class="menu-icon">🎫</text>
        <text class="menu-text">优惠券</text>
        <text class="menu-arrow">></text>
      </navigator>
      
      <navigator url="/pages/user/history" class="menu-item">
        <text class="menu-icon">🕒</text>
        <text class="menu-text">浏览历史</text>
        <text class="menu-arrow">></text>
      </navigator>
      
      <navigator url="/pages/user/settings" class="menu-item">
        <text class="menu-icon">⚙️</text>
        <text class="menu-text">设置</text>
        <text class="menu-arrow">></text>
      </navigator>
      
      <view v-if="isAdmin" class="menu-item" @click="navigateToAdmin">
        <text class="menu-icon">👑</text>
        <text class="menu-text">管理后台</text>
        <text class="menu-arrow">></text>
      </view>
    </view>
    
    <view v-if="isLogin" class="logout-section">
      <button class="logout-btn" @click="handleLogout">退出登录</button>
    </view>
  </view>
</template>

<script>
import { mapState, mapActions } from 'vuex';
import { logout } from '@/services/auth-api';
import { isAdmin } from '@/utils/permission';

export default {
  name: 'UserCenter',
  computed: {
    ...mapState('user', ['userInfo', 'isLogin']),
    isAdmin() {
      return isAdmin();
    }
  },
  onLoad() {
    if (this.isLogin) {
      this.getUserInfo();
    }
  },
  methods: {
    ...mapActions('user', ['getUserInfo', 'logout']),
    
    async handleLogout() {
      uni.showModal({
        title: '确认退出',
        content: '确定要退出登录吗?',
        success: async (res) => {
          if (res.confirm) {
            try {
              await logout();
              this.logout();
              uni.showToast({ title: '已退出登录', icon: 'success' });
            } catch (error) {
              // 即使后端注销失败,也执行本地注销
              this.logout();
              uni.showToast({ title: '已退出登录', icon: 'success' });
            }
          }
        }
      });
    },
    
    navigateToAdmin() {
      uni.navigateTo({
        url: '/pages/admin/index'
      });
    }
  }
};
</script>

<style scoped>
.user-center {
  min-height: 100vh;
  background-color: #f5f5f5;
}

.user-info {
  display: flex;
  align-items: center;
  padding: 32rpx;
  background-color: white;
  margin-bottom: 16rpx;
}

.avatar {
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  margin-right: 24rpx;
}

.user-details {
  flex: 1;
}

.nickname {
  display: block;
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 8rpx;
}

.user-id {
  font-size: 24rpx;
  color: #999;
}

.edit-btn {
  font-size: 24rpx;
  color: #007AFF;
}

.login-prompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 64rpx 32rpx;
  background-color: white;
  margin-bottom: 16rpx;
}

.prompt-text {
  margin: 24rpx 0;
  font-size: 28rpx;
  color: #666;
}

.login-btn {
  padding: 12rpx 32rpx;
  background-color: #007AFF;
  color: white;
  border-radius: 20rpx;
  font-size: 24rpx;
}

.menu-list {
  background-color: white;
  margin-bottom: 16rpx;
}

.menu-item {
  display: flex;
  align-items: center;
  padding: 24rpx 32rpx;
  border-bottom: 1rpx solid #f0f0f0;
}

.menu-item:last-child {
  border-bottom: none;
}

.menu-icon {
  font-size: 32rpx;
  margin-right: 24rpx;
}

.menu-text {
  flex: 1;
  font-size: 28rpx;
}

.menu-arrow {
  font-size: 24rpx;
  color: #999;
}

.logout-section {
  padding: 32rpx;
  margin-top: 32rpx;
}

.logout-btn {
  width: 100%;
  padding: 16rpx;
  background-color: white;
  color: #ff3b30;
  border-radius: 8rpx;
  font-size: 28rpx;
  border: 1rpx solid #ff3b30;
}
</style>

学习目标

通过本教程的学习,你应该能够:

  1. 理解用户管理系统的基本架构和核心概念

    • 掌握用户系统的分层架构
    • 了解不同的用户认证方式
    • 熟悉用户注册和登录流程
  2. 掌握 uni-app 中用户管理的实现方法

    • 学会设计用户数据模型
    • 掌握用户认证API的设计和实现
    • 学会开发登录、注册等核心组件
    • 了解用户状态管理的实现方法
  3. 实现完整的用户管理功能

    • 掌握用户注册和登录功能的实现
    • 了解用户信息管理和更新
    • 学会实现密码重置和修改功能
    • 掌握第三方登录的集成方法
  4. 应用权限管理最佳实践

    • 掌握权限管理系统的设计和实现
    • 了解角色-based权限控制
    • 学会实现权限验证和拦截
  5. 构建安全的用户系统

    • 掌握用户密码加密和安全存储
    • 了解Token管理和刷新机制
    • 学会防止常见的安全漏洞
    • 掌握用户数据的隐私保护

通过本教程的学习,你将能够在 uni-app 中构建功能完善、安全可靠的用户管理系统,为应用提供完整的用户管理能力。

« 上一篇 uni-app 内容管理 下一篇 » uni-app 消息系统