从0到1:健身协会活动小程序开发心得

0 阅读6分钟

背景

近年来,随着生活水平的提高和健康意识的觉醒,“全民健身”已成为社会热点。高校健身协会及社区体育组织举办的活动种类繁多,包括瑜伽课、跑步团、球类比赛等。然而,传统的活动组织模式依赖微信群接龙、Excel表格统计和线下纸质签到,存在以下显著问题:

  • 信息传递低效:活动通知容易被群消息淹没,报名情况无法实时同步。
  • 数据统计困难:人工统计报名人数、签到率及用户活跃度耗时耗力,且易出错。
  • 资源浪费:无法精准控制活动人数,常出现超员或空缺现象。
  • 互动性差:缺乏用户间的互动激励(如打卡分享),难以维持用户粘性。 在此背景下,开发一款轻量级、免安装、易传播的健身协会活动小程序显得尤为重要。利用微信小程序的普及性和云开发的低成本优势,可以实现活动管理的数字化、智能化,提升协会运营效率,增强会员参与感。

系统需求分析

  • 活动策划:管理员制定活动计划,设定时间、地点、人数限制及报名要求。
  • 宣传推广:通过小程序首页、资讯栏目发布活动信息。
  • 用户报名:会员浏览活动,填写必要信息并提交报名。
  • 审核与通知:管理员审核报名资格(如需),系统自动发送通知。
  • 现场签到:活动现场通过扫描二维码核销报名资格。
  • 日常互动:会员每日上传锻炼记录进行打卡,形成社区氛围。
  • 数据复盘:管理员查看活动参与度、用户活跃度报表。

功能需求分析

模块名称功能点详细描述
活动管理活动发布支持设置标题、分类、封面、时间、地点、最大人数、自定义报名表单。
活动列表按时间、分类筛选展示活动,显示剩余名额。
报名处理用户提交报名,系统自动检查名额,管理员可手动审核。
签到核销生成活动专属二维码,管理员扫码完成签到。
每日打卡打卡发布选择打卡项目,输入心得,上传多张图片。
打卡广场展示所有用户的打卡记录,支持点赞(可选)。
统计排行统计用户连续打卡天数,生成排行榜。
资讯中心资讯发布管理员发布健身知识、协会新闻。
资讯浏览用户列表浏览及详情阅读。
个人中心我的报名查看已报名活动的状态(待审核/已通过/已签到)。
我的打卡查看历史打卡记录。
个人信息修改昵称、头像、手机号。
后台管理数据看板可视化展示活动总数、参与人次、新增用户等。
用户管理查看用户列表,禁用违规用户。

在这里插入图片描述

系统设计

系统采用典型的分层架构,基于微信云开发实现前后端一体化:

  • 表现层(Client):微信小程序端,负责UI渲染与用户交互。
  • 逻辑层(Cloud Functions):部署在云端的Node.js函数,处理核心业务逻辑(报名原子操作、权限校验、定时任务)。
  • 数据层(Cloud Database & Storage):
  • 云数据库:存储结构化业务数据。
  • 云存储:存储非结构化多媒体文件。
  • 基础设施层:腾讯云服务器集群,由微信平台自动运维。

数据库设计

字段名数据类型必填默认值说明
_idString-主键,用户的微信 OpenID (如: openid_xxxxx)
nickNameString-用户昵称
avatarUrlString-用户头像图片的云存储地址 (FileID)
genderNumber0性别:0-未知,1-男,2-女
phoneString-用户手机号 (需授权获取)
roleString"user"用户角色:user (普通会员), admin (管理员)
createTimeDate-用户首次注册时间
lastLoginTimeDate-最后登录时间
字段名数据类型必填默认值说明
_idString-主键,活动唯一标识 (如: act_20231001)
titleString-活动标题
categoryIdString-活动分类 ID (关联分类配置)
coverImageString-活动封面图云存储地址 (FileID)
contentString-活动详细介绍 (支持富文本 HTML)
startTimeDate-活动开始时间
endTimeDate-活动结束时间
locationString-活动地点
maxCountNumber-最大报名人数限制
currentCountNumber0当前已报名人数 (通过原子操作更新)
statusNumber1活动状态:1-报名中, 2-进行中, 3-已结束, 4-已取消
customFieldsArray[]自定义报名表单配置,例:[{label:"身份证", type:"text", required:true}]
publisherIdString-发布该活动的管理员 OpenID
createTimeDate-活动创建时间
signQrCodeString-签到专用二维码内容 (通常为活动 ID)
字段名数据类型必填默认值说明
_idString-主键,报名记录唯一标识
activityIdString-外键,关联 activities 表的 _id
userIdString-外键,关联 users 表的 _id (OpenID)
userNameString-报名时的用户昵称 (冗余字段,便于查询)
userPhoneString-报名时的用户手机号
formDataObject{}用户填写的自定义表单数据,例:{"身份证": "110...", "备注": "..."}
statusNumber0报名状态:0-待审核, 1-已通过, 2-已拒绝, 3-已签到
signTimeDate-实际签到时间 (当 status 变为 3 时记录)
createTimeDate-提交报名时间
auditTimeDate-管理员审核时间
字段名数据类型必填默认值说明
_idString-主键,打卡记录唯一标识
userIdString-外键,关联 users 表的 _id
userNameString-用户昵称 (冗余字段)
checkDateString-打卡日期字符串 (格式: YYYY-MM-DD),用于去重统计
itemIdString-打卡项目 ID (如: run_5km, yoga_30min)
itemNameString-打卡项目名称 (如: "晨跑 5 公里")
contentString-用户填写的锻炼心得或描述
imagesArray[]上传图片的云存储地址列表 (FileID 数组)
likeCountNumber0点赞数量
createTimeDate-打卡提交时间

代码片段

// cloudfunctions/m_cloud/index.js
if (event.action === 'createActivity') {
  const { activityData } = event;
  // 校验管理员权限
  const user = await db.collection('users').doc(openid).get();
  if (!user.data || user.data.role !== 'admin') {
    return { code: 403, msg: '无权限' };
  }
  
  // 初始化人数
  activityData.currentCount = 0;
  activityData.status = 1;
  activityData.createTime = db.serverDate();
  
  const res = await db.collection('activities').add({ data: activityData });
  return { code: 200, data: res._id };
}

if (event.action === 'signUp') {
  const { activityId, formData } = event;
  
  // 开启事务或使用原子操作
  // 方案:先查询,再更新。但在高并发下需用数据库原子指令 _.inc
  // 此处采用:先检查是否已报,再利用 update 的原子性扣减名额
  
  const checkRes = await db.collection('activity_signs')
    .where({ activityId, userId: openid }).get();
  if (checkRes.data.length > 0) return { code: 400, msg: '请勿重复报名' };

  // 尝试更新活动人数:条件是当前人数 < 最大人数
  const updateRes = await db.collection('activities').doc(activityId).update({
    data: {
      currentCount: _.inc(1)
    },
    // 注意:云开发单条update不支持条件判断,需在逻辑层控制或利用事务
    // 简化版实现:先查后改,配合云函数串行特性(大部分场景够用)
    // 严谨版需使用 db.runTransaction
  });
  
  // 严谨的事务写法示例
  try {
    await db.runTransaction(async (transaction) => {
      const actRef = transaction.collection('activities').doc(activityId);
      const actSnap = await transaction.get(actRef);
      const actData = actSnap.data;
      
      if (actData.currentCount >= actData.maxCount) {
        throw new Error('FULL');
      }
      
      // 写入报名记录
      await transaction.collection('activity_signs').add({
        data: {
          activityId, userId: openid, formData, status: 0, createTime: db.serverDate()
        }
      });
      
      // 更新人数
      await transaction.update(actRef, { currentCount: _.inc(1) });
    });
    return { code: 200, msg: '报名成功' };
  } catch (e) {
    if (e.message === 'FULL') return { code: 400, msg: '名额已满' };
    throw e;
  }
}

UI设计

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

后台管理系统设计

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

git代码下载

下载点击