使用uniapp+vant做一个h5前端网站
基于uni-app的健身会员管理系统前端技术分析
项目背景
- 本来是打算做一个会员管理的wx小程序,由于我开始前未弄清楚wx小程序原生语言和uniapp都分别支持什么组件,只知道uniapp可以开发小程序,直接就使用了vue3+vant4组件。
- 做到最后要打包为微信小程序时傻了眼,成堆报错,查了查错误后才知道几乎都是vant4组件的错,根本用不了。
- 查了查才知道vant weapp是专门用于小程序和app的组件,所以我就全部替换了,发现还是不行:页内跳转方法不对,底部导航栏导航方法也不对。。。
- 于是索性不改了,还好备份过,h5就h5吧。
- 业主后来想通过微信小程序来给他的会员发送到期请续费的通知(服务号通知)。但是经过我对这块狠狠地补习后,我发现目前的系统并不能做到这点,需要让会员们通过微信授权登录进来(但是感觉纯H5网站实现微信授权登录挺麻烦的),这样的话可以获取到他们的微信用户信息,就能通过服务号发送给每位授权登录的会员。
- 我突然想到可不可以使用微信原生框架开发一个只做登录,登录的请求和返回都对应做好的这个h5网站,这样的话既能用微信官方登录接口,登录后的画面是这个h5网站,两全其美。
- 但是哪有我想的这莫简单,微信虽然有一个
<webview>
可以做外部网站的显示容器,但是需要设置授权,所以行不通。后面我会单独发一篇文章,关于微信小程序开发方式以及我碰到的全部麻烦。 - 最后我找到了一个公众号,专门转发这种通知,但有限制。所以最后到期通知只能发给业主自己,且没有会员登录的功能。后面会发一篇后端和数据库的实现以及如何部署。
项目概述
这是一个基于uni-app开发的健身会员管理系统,主要用于管理会员信息、课程进度以及发送到期提醒等功能。项目采用了Vue 3 + Vite的技术栈,整体架构清晰,代码组织规范。
技术栈
- 前端框架:Vue 3
- 构建工具:Vite
- UI组件库:Vant weapp
- HTTP请求:Axios
- 跨端框架:uni-app
API设计与实现
1. 请求配置与拦截器
项目使用了axios进行HTTP请求,并配置了完整的请求拦截和响应拦截机制。让我们来看看具体的实现:
// 后端API配置
const backendAPI = {
baseUrl: "/api",
}
// 创建axios实例
const instance = axios.create({
baseURL: backendAPI.baseUrl,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
withCredentials: true
})
这段代码创建了一个axios实例,设置了基本的请求配置:
baseURL
: 设置API的基础路径timeout
: 设置请求超时时间为30秒headers
: 设置请求头,指定内容类型为JSONwithCredentials
: 允许跨域请求携带cookie
API路径配置详解
在代码中出现的 /api
是一个API的基础路径(base URL),它表示:
代理路径:
- 在开发环境中,这个
/api
通常会被配置为代理路径 - 比如当你在本地开发时,请求
/api/member/list
实际上会被代理到http://localhost:3000/member/list
这样的实际后端地址
- 在开发环境中,这个
实际作用:
- 它作为所有API请求的前缀
- 比如
getMemberList
方法实际请求的完整URL是/api/member/list
- 这样可以统一管理所有API的路径
配置方式:
在
vite.config.js
中通常会配置代理,类似这样:server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } }
实际部署时:
- 在生产环境中,这个
/api
可能会指向实际的服务器地址 - 比如
https://api.yourdomain.com
- 在生产环境中,这个
这样设计的好处是:
- 开发时可以使用代理避免跨域问题
- 部署时只需要修改代理配置,不需要改代码
统一管理所有API请求的路径前缀
2. 请求拦截器
instance.interceptors.request.use(
config => {
const token = uni.getStorageSync('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token.trim()}`
}
return config
},
error => {
return Promise.reject(error)
}
)
请求拦截器的主要作用是:
- 在每次请求前自动添加token到请求头
- 使用
uni.getStorageSync
获取本地存储的token - 如果存在token,则添加到请求头的Authorization字段
3. 响应拦截器
instance.interceptors.response.use(
response => {
return response.data;
},
async error => {
// 错误处理逻辑
if (!error.response) {
uni.showToast({
title: '网络连接失败,请检查网络设置',
icon: 'none',
duration: 2000
});
}
// ... 其他错误处理
return Promise.reject(error);
}
)
响应拦截器的主要功能:
- 统一处理响应数据,直接返回response.data
- 处理各种错误情况,如网络错误、服务器错误等
- 使用uni-app的showToast方法展示错误提示
4. API方法封装
项目将所有的API请求方法进行了统一封装,例如:
export const {
adminLogin,
getAdminProfile,
getMemberList,
// ... 其他方法
} = {
adminLogin: (username, password) => instance.post('/admin/login', { username, password })
.then(response => {
if (response.code === 200 && response.data.token) {
uni.setStorageSync('token', response.data.token)
uni.setStorageSync('adminInfo', response.data.admin)
return response
}
return Promise.reject(new Error(response.message || '登录失败'))
}),
getAdminProfile: () => instance.get('/admin/profile'),
getMemberList: () => instance.get('/member/list'),
// ... 其他方法实现
}
这种封装方式的优点:
- 统一管理所有API请求
- 每个方法都有清晰的错误处理
- 登录成功后自动保存token和用户信息
- 使用Promise链式调用,代码更易读
5. 权限管理实现
项目中使用了基于token的权限管理机制,主要通过以下方式实现:
登录认证:
adminLogin: (username, password) => instance.post('/admin/login', { username, password }) .then(response => { if (response.code === 200 && response.data.token) { // 保存token和用户信息 uni.setStorageSync('token', response.data.token) uni.setStorageSync('adminInfo', response.data.admin) uni.setStorageSync('tokenExpireTime', Date.now() + `365 *` 24 * 60 * 60 * 1000) return response } return Promise.reject(new Error(response.message || '登录失败')) })
Token验证:
- 每次请求都会在请求头中携带token
通过请求拦截器自动添加token
instance.interceptors.request.use( config => { const token = uni.getStorageSync('token') if (token) { config.headers['Authorization'] = `Bearer ${token.trim()}` } return config } )
权限控制:
401错误处理:当token无效或过期时,自动跳转到登录页
if (error.response.status === 401) { uni.removeStorageSync('token') uni.removeStorageSync('adminInfo') uni.removeStorageSync('tokenExpireTime') uni.showToast({ title: '登录已过期,请重新登录', icon: 'none', duration: 2000 }); setTimeout(() => { uni.reLaunch({ url: '/pages/login/index' }); }, 2000); }
权限特点:
- 使用JWT(JSON Web Token)进行身份验证
- token有效期设置为1年(我设置的
*365
,默认是一天) - 自动处理token过期情况
- 统一的权限错误处理机制
- 登录状态持久化存储
安全措施:
- token存储在本地存储中
- 请求时自动添加Bearer认证
- 敏感操作需要重新验证
- 登出时清除所有认证信息
这种权限管理方式的优点:
- 无状态:服务器不需要存储session
- 安全性:使用JWT保证数据完整性
- 灵活性:可以携带用户信息
- 可扩展:易于添加新的权限控制
- 用户体验:自动处理登录状态
系统权限扩展建议
由于该系统我只实现了管理员登录,如果要支持普通用户和会员登录,可以按以下方式扩展(仅供参考):
用户角色设计(数据库设计也可以按此方式):
// 用户角色分类 const UserRole = { ADMIN: 'admin', // 管理员 MEMBER: 'member', // 会员 USER: 'user' // 普通用户 } // 权限级别 const PermissionLevel = { SUPER_ADMIN: 3, // 超级管理员 ADMIN: 2, // 普通管理员 MEMBER: 1, // 会员 USER: 0 // 普通用户 }
登录接口扩展:
// 扩展登录方法 export const { userLogin, // 普通用户登录 memberLogin, // 会员登录 adminLogin // 管理员登录 } = { userLogin: (username, password) => instance.post('/user/login', { username, password }) .then(response => { if (response.code === 200) { uni.setStorageSync('token', response.data.token) uni.setStorageSync('userInfo', response.data.user) uni.setStorageSync('userRole', UserRole.USER) return response } return Promise.reject(new Error(response.message || '登录失败')) }), memberLogin: (username, password) => instance.post('/member/login', { username, password }) .then(response => { if (response.code === 200) { uni.setStorageSync('token', response.data.token) uni.setStorageSync('memberInfo', response.data.member) uni.setStorageSync('userRole', UserRole.MEMBER) return response } return Promise.reject(new Error(response.message || '登录失败')) }) }
权限验证中间件:
// 权限验证工具 const checkPermission = (requiredLevel) => { const userRole = uni.getStorageSync('userRole') const currentLevel = PermissionLevel[userRole.toUpperCase()] return currentLevel >= requiredLevel } // 路由守卫 const routerGuard = (to, from, next) => { const token = uni.getStorageSync('token') if (!token) { next('/login') return } // 根据路由meta信息检查权限 if (to.meta.requiredLevel && !checkPermission(to.meta.requiredLevel)) { uni.showToast({ title: '权限不足', icon: 'none' }) next(from.path) return } next() }
API权限控制:
// 请求拦截器中添加角色验证 instance.interceptors.request.use( config => { const token = uni.getStorageSync('token') const userRole = uni.getStorageSync('userRole') if (token) { config.headers['Authorization'] = `Bearer ${token.trim()}` config.headers['X-User-Role'] = userRole } return config } )
功能权限控制示例:
// 会员管理功能 const memberManagement = { // 只有管理员可以查看所有会员 getAllMembers: () => { if (!checkPermission(PermissionLevel.ADMIN)) { return Promise.reject(new Error('权限不足')) } return instance.get('/member/list') }, // 会员可以查看自己的信息 getMemberInfo: (id) => { const currentUserId = uni.getStorageSync('memberInfo')?.id if (id !== currentUserId && !checkPermission(PermissionLevel.ADMIN)) { return Promise.reject(new Error('权限不足')) } return instance.get(`/member/${id}`) } }
页面权限控制:
// 页面配置示例 const pages = [ { path: '/admin/dashboard', meta: { requiredLevel: PermissionLevel.ADMIN, title: '管理后台' } }, { path: '/member/profile', meta: { requiredLevel: PermissionLevel.MEMBER, title: '会员中心' } }, { path: '/user/home', meta: { requiredLevel: PermissionLevel.USER, title: '用户首页' } } ]
6. 微信通知功能
项目中还实现了微信通知功能 (我这里是用的是一个第三方服务号发送的通知),用于提醒会员到期:
sendWechatNotification: (expiringMembers, expiredMembers) => {
const token = 'dyhrw3Uf5Q*****8orF7J8yW0';
const title = 'XX健身-会员到期提醒';
// 格式化日期
const currentDate = new Date().toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
// 构建通知内容
let content = `提醒时间: ${currentDate}\n\n`;
// ... 构建会员信息内容
// 发送请求
return new Promise((resolve, reject) => {
uni.request({
url: `/api/wx-push/${token}.send`,
method: 'POST',
data: {
text: title,
desp: content
},
success: (res) => resolve(res.data),
fail: (err) => reject(err)
});
});
}
这个功能的特点:
- 使用uni.request发送请求
- 支持Promise异步处理
- 格式化日期显示
- 构建结构化的通知内容
总结
这个项目展示了如何在一个uni-app项目中:
- 使用axios进行API请求管理
- 实现完整的请求拦截和响应拦截
- 统一处理错误和提示
- 封装API方法
- 实现微信通知等特色功能
对于想要学习uni-app开发的同学来说,这个项目是一个很好的参考案例。它展示了如何组织代码、处理API请求、管理状态等实际开发中常见的问题。
学习建议
- 先理解项目的基本架构和目录结构
- 重点学习API请求的封装方式
- 掌握错误处理和拦截器的使用
- 了解uni-app特有的API(如uni.request、uni.showToast等)
- 尝试自己实现一些简单的功能,如会员信息的增删改查