/www/wwwroot/blog.ybyq.wang/usr/plugins/AMP/templates/MIPpage.php on line 33
">

使用uniapp+vant做一个h5前端网站

2025-04-17T01:52:00

基于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: 设置请求头,指定内容类型为JSON
  • withCredentials: 允许跨域请求携带cookie

API路径配置详解

在代码中出现的 /api 是一个API的基础路径(base URL),它表示:

  1. 代理路径

    • 在开发环境中,这个 /api 通常会被配置为代理路径
    • 比如当你在本地开发时,请求 /api/member/list 实际上会被代理到 http://localhost:3000/member/list 这样的实际后端地址
  2. 实际作用

    • 它作为所有API请求的前缀
    • 比如 getMemberList 方法实际请求的完整URL是 /api/member/list
    • 这样可以统一管理所有API的路径
  3. 配置方式

    • vite.config.js 中通常会配置代理,类似这样:

      server: {
      proxy: {
        '/api': {
          target: 'http://localhost:3000',
          changeOrigin: true
        }
      }
      }
  4. 实际部署时

    • 在生产环境中,这个 /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的权限管理机制,主要通过以下方式实现:

  1. 登录认证

    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 || '登录失败'))
     })
  2. Token验证

    • 每次请求都会在请求头中携带token
    • 通过请求拦截器自动添加token

      instance.interceptors.request.use(
       config => {
         const token = uni.getStorageSync('token')
         if (token) {
             config.headers['Authorization'] = `Bearer ${token.trim()}`
         }
         return config
       }
      )
  3. 权限控制

    • 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);
      }
  4. 权限特点

    • 使用JWT(JSON Web Token)进行身份验证
    • token有效期设置为1年(我设置的*365,默认是一天)
    • 自动处理token过期情况
    • 统一的权限错误处理机制
    • 登录状态持久化存储
  5. 安全措施

    • token存储在本地存储中
    • 请求时自动添加Bearer认证
    • 敏感操作需要重新验证
    • 登出时清除所有认证信息

这种权限管理方式的优点:

  • 无状态:服务器不需要存储session
  • 安全性:使用JWT保证数据完整性
  • 灵活性:可以携带用户信息
  • 可扩展:易于添加新的权限控制
  • 用户体验:自动处理登录状态

系统权限扩展建议

由于该系统我只实现了管理员登录,如果要支持普通用户和会员登录,可以按以下方式扩展(仅供参考):

  1. 用户角色设计(数据库设计也可以按此方式)

    // 用户角色分类
    const UserRole = {
     ADMIN: 'admin',      // 管理员
     MEMBER: 'member',    // 会员
     USER: 'user'         // 普通用户
    }
    
    // 权限级别
    const PermissionLevel = {
     SUPER_ADMIN: 3,      // 超级管理员
     ADMIN: 2,            // 普通管理员
     MEMBER: 1,           // 会员
     USER: 0              // 普通用户
    }
  2. 登录接口扩展

    // 扩展登录方法
    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 || '登录失败'))
         })
    }
  3. 权限验证中间件

    // 权限验证工具
    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()
    }
  4. 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
     }
    )
  5. 功能权限控制示例

    // 会员管理功能
    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}`)
     }
    }
  6. 页面权限控制

    // 页面配置示例
    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项目中:

  1. 使用axios进行API请求管理
  2. 实现完整的请求拦截和响应拦截
  3. 统一处理错误和提示
  4. 封装API方法
  5. 实现微信通知等特色功能

对于想要学习uni-app开发的同学来说,这个项目是一个很好的参考案例。它展示了如何组织代码、处理API请求、管理状态等实际开发中常见的问题。

学习建议

  1. 先理解项目的基本架构和目录结构
  2. 重点学习API请求的封装方式
  3. 掌握错误处理和拦截器的使用
  4. 了解uni-app特有的API(如uni.request、uni.showToast等)
  5. 尝试自己实现一些简单的功能,如会员信息的增删改查
当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »