uni-app 用户管理
核心知识点
1. 用户系统架构
一个完整的用户管理系统通常包括以下几个核心部分:
- 用户认证模块:负责用户注册、登录、注销等功能
- 用户信息模块:负责用户个人信息的管理和更新
- 权限管理模块:负责用户权限的分配和控制
- 用户状态管理:负责用户登录状态的维护和管理
- 安全保障模块:负责用户数据的安全和隐私保护
2. 用户认证方式
在 uni-app 中,我们可以实现多种用户认证方式:
- 账号密码登录:传统的用户名/邮箱/手机号 + 密码登录
- 短信验证码登录:通过手机号接收验证码进行登录
- 第三方登录:集成微信、支付宝、QQ等第三方平台登录
- 生物识别登录:集成指纹、面容识别等生物特征登录
- Token 登录:使用 JWT 等 Token 进行无状态登录
3. 用户注册流程
一个完整的用户注册流程通常包括以下步骤:
- 用户信息收集:收集用户名、密码、邮箱、手机号等信息
- 信息验证:验证用户输入的信息是否符合要求
- 短信/邮箱验证:通过短信或邮箱验证码验证用户身份
- 密码加密:对用户密码进行加密存储
- 用户创建:在数据库中创建用户记录
- 注册成功:返回注册成功信息,引导用户登录
4. 用户登录流程
用户登录流程通常包括以下步骤:
- 用户身份验证:验证用户输入的账号和密码是否正确
- Token 生成:生成用户登录凭证(如 JWT Token)
- 登录状态保存:将登录状态保存到本地存储
- 用户信息获取:获取用户详细信息
- 登录成功:返回登录成功信息,跳转到首页
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>学习目标
通过本教程的学习,你应该能够:
理解用户管理系统的基本架构和核心概念
- 掌握用户系统的分层架构
- 了解不同的用户认证方式
- 熟悉用户注册和登录流程
掌握 uni-app 中用户管理的实现方法
- 学会设计用户数据模型
- 掌握用户认证API的设计和实现
- 学会开发登录、注册等核心组件
- 了解用户状态管理的实现方法
实现完整的用户管理功能
- 掌握用户注册和登录功能的实现
- 了解用户信息管理和更新
- 学会实现密码重置和修改功能
- 掌握第三方登录的集成方法
应用权限管理最佳实践
- 掌握权限管理系统的设计和实现
- 了解角色-based权限控制
- 学会实现权限验证和拦截
构建安全的用户系统
- 掌握用户密码加密和安全存储
- 了解Token管理和刷新机制
- 学会防止常见的安全漏洞
- 掌握用户数据的隐私保护
通过本教程的学习,你将能够在 uni-app 中构建功能完善、安全可靠的用户管理系统,为应用提供完整的用户管理能力。