从 0 到 1:活动中心小程序开发笔记(上)
 作者:CC同学
- 2025-06-19  广东
- 本文字数:8053 字 - 阅读完需:约 26 分钟 
概要设计
- 方便居民和社区工作人员进行互动、参与活动、获取信息等,提供一站式服务的平台。 
- 活动发布与报名:展示社区内即将举行的各类活动(如文化活动、健身活动、志愿服务等),用户可以浏览活动信息并进行报名 
- 通知与公告:向居民发布社区的重要通知和公告,内容可能包括社区建设、物业管理、卫生检查等方面的信息。用户也能查看历史公告,保持对社区事务的知情权。 
- 互动与反馈:为社区居民提供一个互动平台,居民可以对活动、服务、管理等方面提出意见或反馈,或进行投诉处理。管理员可根据反馈内容及时处理和回应。 
功能规划
 
 数据库设计
ActivityModel.DB_STRUCTURE = {  _pid: 'string|true',  ACTIVITY_ID: 'string|true',
  ACTIVITY_TITLE: 'string|true|comment=标题',  ACTIVITY_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中',
  ACTIVITY_CATE_ID: 'string|true|default=0|comment=分类',  ACTIVITY_CATE_NAME: 'string|false|comment=分类冗余',
  ACTIVITY_CANCEL_SET: 'int|true|default=1|comment=取消设置 0=不允,1=允许,2=仅截止前可取消',  ACTIVITY_CHECK_SET: 'int|true|default=0|comment=审核 0=不需要审核,1=需要审核',   ACTIVITY_IS_MENU: 'int|true|default=1|comment=是否公开展示名单',
  ACTIVITY_MAX_CNT: 'int|true|default=20|comment=人数上限 0=不限',  ACTIVITY_START: 'int|false|comment=活动开始时间',  ACTIVITY_END: 'int|false|comment=活动截止时间',  ACTIVITY_STOP: 'int|true|default=0|comment=报名截止时间 0=永不过期',
  ACTIVITY_ORDER: 'int|true|default=9999',  ACTIVITY_VOUCH: 'int|true|default=0',
  ACTIVITY_FORMS: 'array|true|default=[]',  ACTIVITY_OBJ: 'object|true|default={}',
  ACTIVITY_JOIN_FORMS: 'array|true|default=[]',
  ACTIVITY_ADDRESS: 'string|false|comment=详细地址',  ACTIVITY_ADDRESS_GEO: 'object|false|comment=详细地址坐标参数',
  ACTIVITY_QR: 'string|false',  ACTIVITY_VIEW_CNT: 'int|true|default=0',  ACTIVITY_JOIN_CNT: 'int|true|default=0',  ACTIVITY_COMMENT_CNT: 'int|true|default=0',
  ACTIVITY_USER_LIST: 'array|true|default=[]|comment={name,id,pic}',
  ACTIVITY_ADD_TIME: 'int|true',  ACTIVITY_EDIT_TIME: 'int|true',  ACTIVITY_ADD_IP: 'string|false',  ACTIVITY_EDIT_IP: 'string|false',};
ActivityJoinModel.DB_STRUCTURE = {  _pid: 'string|true',  ACTIVITY_JOIN_ID: 'string|true',  ACTIVITY_JOIN_ACTIVITY_ID: 'string|true|comment=报名PK',
  ACTIVITY_JOIN_IS_ADMIN: 'int|true|default=0|comment=是否管理员添加 0/1',
  ACTIVITY_JOIN_CODE: 'string|true|comment=核验码15位',  ACTIVITY_JOIN_IS_CHECKIN: 'int|true|default=0|comment=是否签到 0/1 ',  ACTIVITY_JOIN_CHECKIN_TIME: 'int|false|default=0|签到时间',
  ACTIVITY_JOIN_USER_ID: 'string|true|comment=用户ID',
  ACTIVITY_JOIN_FORMS: 'array|true|default=[]|comment=表单',  ACTIVITY_JOIN_OBJ: 'object|true|default={}',
  ACTIVITY_JOIN_STATUS: 'int|true|default=1|comment=状态  0=待审核 1=报名成功, 99=审核未过',  ACTIVITY_JOIN_REASON: 'string|false|comment=审核拒绝或者取消理由',
  ACTIVITY_JOIN_ADD_TIME: 'int|true',  ACTIVITY_JOIN_EDIT_TIME: 'int|true',  ACTIVITY_JOIN_ADD_IP: 'string|false',  ACTIVITY_JOIN_EDIT_IP: 'string|false',};CommentModel.DB_STRUCTURE = {  _pid: 'string|true',
  COMMENT_ID: 'string|true',
  COMMENT_USER_ID: 'string|true|comment=用户ID',
  COMMENT_TYPE: 'string|true',  COMMENT_OID: 'string|true',     COMMENT_FORMS: 'array|true|default=[]',  COMMENT_OBJ: 'object|true|default={}',
  COMMENT_ADD_TIME: 'int|true',  COMMENT_EDIT_TIME: 'int|true',  COMMENT_ADD_IP: 'string|false',  COMMENT_EDIT_IP: 'string|false',
};
复制代码
 核心实现
class ActivityService extends BaseProjectService {
  // 获取当前活动状态  getJoinStatusDesc(activity) {    let timestamp = this._timestamp;
    if (activity.ACTIVITY_STATUS == 0)      return '活动停止';    else if (activity.ACTIVITY_END <= timestamp)      return '活动结束';    else if (activity.ACTIVITY_STOP <= timestamp)      return '报名结束';    else if (activity.ACTIVITY_MAX_CNT > 0      && activity.ACTIVITY_JOIN_CNT >= activity.ACTIVITY_MAX_CNT)      return '报名已满';    else      return '报名中';  }
  /** 浏览信息 */  async viewActivity(userId, id) {
    let fields = '*';
    let where = {      _id: id,      ACTIVITY_STATUS: ActivityModel.STATUS.COMM    }    let activity = await ActivityModel.getOne(where, fields);    if (!activity) return null;
    ActivityModel.inc(id, 'ACTIVITY_VIEW_CNT', 1);
    // 判断是否有报名    let whereJoin = {      ACTIVITY_JOIN_USER_ID: userId,      ACTIVITY_JOIN_ACTIVITY_ID: id,      ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]    }    let activityJoin = await ActivityJoinModel.getOne(whereJoin);    if (activityJoin) {      activity.myActivityJoinId = activityJoin._id;      activity.myActivityJoinTag = (activityJoin.ACTIVITY_JOIN_STATUS == ActivityJoinModel.STATUS.WAIT) ? '待审核' : '已报名';    }
    else {      activity.myActivityJoinId = '';      activity.myActivityJoinTag = '';    }
    return activity;  }
  /** 取得分页列表 */  async getActivityList({    cateId, //分类查询条件    search, // 搜索条件    sortType, // 搜索菜单    sortVal, // 搜索菜单    orderBy, // 排序     page,    size,    isTotal = true,    oldTotal  }) {
    orderBy = orderBy || {      'ACTIVITY_ORDER': 'asc',      'ACTIVITY_ADD_TIME': 'desc'    };    let fields = 'ACTIVITY_CATE_NAME,ACTIVITY_USER_LIST,ACTIVITY_STOP,ACTIVITY_JOIN_CNT,ACTIVITY_OBJ,ACTIVITY_VIEW_CNT,ACTIVITY_TITLE,ACTIVITY_MAX_CNT,ACTIVITY_START,ACTIVITY_END,ACTIVITY_ORDER,ACTIVITY_STATUS,ACTIVITY_CATE_NAME,ACTIVITY_OBJ';
    let where = {};    where.and = {      _pid: this.getProjectId() //复杂的查询在此处标注PID    };    if (cateId && cateId !== '0') where.and.ACTIVITY_CATE_ID = cateId;
    where.and.ACTIVITY_STATUS = ActivityModel.STATUS.COMM; // 状态  
    if (util.isDefined(search) && search) {      where.or = [{        ACTIVITY_TITLE: ['like', search]      },];    } else if (sortType && util.isDefined(sortVal)) {      // 搜索菜单      switch (sortType) {        case 'cateId': {          if (sortVal) where.and.ACTIVITY_CATE_ID = String(sortVal);          break;        }        case 'sort': {          // 排序          orderBy = this.fmtOrderBySort(sortVal, 'ACTIVITY_ADD_TIME');          break;        }        case 'today': { //今天          let start = timeUtil.getDayFirstTimestamp();          let end = start + 86400 * 1000 - 1;          where.and.ACTIVITY_START = ['between', start, end];          break;        }        case 'tomorrow': { //明日          let start = timeUtil.getDayFirstTimestamp() + 86400 * 1000;          let end = start + 86400 * 1000 - 1;          where.and.ACTIVITY_START = ['between', start, end];          break;        }        case 'month': { //本月          let day = timeUtil.time('Y-M-D');          let start = timeUtil.getMonthFirstTimestamp(day);          let end = timeUtil.getMonthLastTimestamp(day);
          where.and.ACTIVITY_START = ['between', start, end];          break;        }      }    }
    return await ActivityModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);  }
  /** 取得某一个报名分页列表 */  async getActivityJoinList(activityId, {    search, // 搜索条件    sortType, // 搜索菜单    sortVal, // 搜索菜单    orderBy, // 排序     page,    size,    isTotal = true,    oldTotal  }) {    orderBy = orderBy || {      'ACTIVITY_JOIN_ADD_TIME': 'desc'    };    let fields = 'ACTIVITY_JOIN_OBJ,ACTIVITY_JOIN_IS_CHECKIN,ACTIVITY_JOIN_REASON,ACTIVITY_JOIN_ACTIVITY_ID,ACTIVITY_JOIN_STATUS,ACTIVITY_JOIN_ADD_TIME,user.USER_PIC,user.USER_NAME,user.USER_OBJ';
    let where = {      ACTIVITY_JOIN_ACTIVITY_ID: activityId,      ACTIVITY_JOIN_STATUS: ActivityModel.STATUS.COMM    };
    let joinParams = {      from: UserModel.CL,      localField: 'ACTIVITY_JOIN_USER_ID',      foreignField: 'USER_MINI_OPENID',      as: 'user',    };
    let result = await ActivityJoinModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);
    return result;  }
  /** 取得我的报名分页列表 */  async getMyActivityJoinList(userId, {    search, // 搜索条件    sortType, // 搜索菜单    sortVal, // 搜索菜单    orderBy, // 排序     page,    size,    isTotal = true,    oldTotal  }) {    orderBy = orderBy || {      'ACTIVITY_JOIN_ADD_TIME': 'desc'    };    let fields = 'ACTIVITY_JOIN_IS_CHECKIN,ACTIVITY_JOIN_REASON,ACTIVITY_JOIN_ACTIVITY_ID,ACTIVITY_JOIN_STATUS,ACTIVITY_JOIN_ADD_TIME,activity.ACTIVITY_END,activity.ACTIVITY_START,activity.ACTIVITY_TITLE';
    let where = {      ACTIVITY_JOIN_USER_ID: userId    };
    if (util.isDefined(search) && search) {      where['activity.ACTIVITY_TITLE'] = {        $regex: '.*' + search,        $options: 'i'      };    } else if (sortType) {      // 搜索菜单      switch (sortType) {        case 'timedesc': { //按时间倒序          orderBy = {            'activity.ACTIVITY_START': 'desc',            'ACTIVITY_JOIN_ADD_TIME': 'desc'          };          break;        }        case 'timeasc': { //按时间正序          orderBy = {            'activity.ACTIVITY_START': 'asc',            'ACTIVITY_JOIN_ADD_TIME': 'asc'          };          break;        }        case 'succ': {          where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.SUCC;          break;        }        case 'wait': {          where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.WAIT;          break;        }        case 'cancel': {          where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.ADMIN_CANCEL;          break;        }      }    }
    let joinParams = {      from: ActivityModel.CL,      localField: 'ACTIVITY_JOIN_ACTIVITY_ID',      foreignField: '_id',      as: 'activity',    };
    let result = await ActivityJoinModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);
    return result;  }
  /** 取得我的报名详情 */  async getMyActivityJoinDetail(userId, activityJoinId) {
    let fields = '*';
    let where = {      _id: activityJoinId,      ACTIVITY_JOIN_USER_ID: userId    };    let activityJoin = await ActivityJoinModel.getOne(where, fields);    if (activityJoin) {      activityJoin.activity = await ActivityModel.getOne(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID, 'ACTIVITY_TITLE,ACTIVITY_START,ACTIVITY_END');    }    return activityJoin;  }
  //################## 报名  
  async statActivityJoin(id) {        // 报名数    let where = {      ACTIVITY_JOIN_ACTIVITY_ID: id,      ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]    }    let cnt = await ActivityJoinModel.count(where);
        // 用户列表    where = {      ACTIVITY_JOIN_ACTIVITY_ID: id,      ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC    }    let joinParams = {      from: UserModel.CL,      localField: 'ACTIVITY_JOIN_USER_ID',      foreignField: 'USER_MINI_OPENID',      as: 'user',    };    let orderBy = {      ACTIVITY_JOIN_ADD_TIME: 'desc'    }    let list = await ActivityJoinModel.getListJoin(joinParams, where, 'ACTIVITY_JOIN_ADD_TIME,user.USER_MINI_OPENID,user.USER_NAME,user.USER_PIC', orderBy, 1, 6, false, 0);    list = list.list;
    for (let k = 0; k < list.length; k++) {      list[k] = list[k].user;    } 
    await ActivityModel.edit(id, { ACTIVITY_JOIN_CNT: cnt, ACTIVITY_USER_LIST: list });  }
  /**  报名前获取关键信息 */  async detailForActivityJoin(userId, activityId) {    let fields = 'ACTIVITY_JOIN_FORMS, ACTIVITY_TITLE';
    let where = {      _id: activityId,      ACTIVITY_STATUS: ActivityModel.STATUS.COMM    }    let activity = await ActivityModel.getOne(where, fields);    if (!activity)      this.AppError('该活动不存在');
    // 取出本人最近一次的填写表单
    let whereMy = {      ACTIVITY_JOIN_USER_ID: userId,    }    let orderByMy = {      ACTIVITY_JOIN_ADD_TIME: 'desc'    }    let joinMy = await ActivityJoinModel.getOne(whereMy, 'ACTIVITY_JOIN_FORMS', orderByMy);
    let myForms = joinMy ? joinMy.ACTIVITY_JOIN_FORMS : [];    activity.myForms = myForms;
    return activity;  }
  /** 取消我的报名 只有成功和待审核可以取消 取消即为删除记录 */  async cancelMyActivityJoin(userId, activityJoinId) {    let where = {      ACTIVITY_JOIN_USER_ID: userId,      _id: activityJoinId,      ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]    };    let activityJoin = await ActivityJoinModel.getOne(where);
    if (!activityJoin) {      this.AppError('未找到可取消的报名记录');    }
    if (activityJoin.ACTIVITY_JOIN_IS_CHECKIN == 1)      this.AppError('该活动已经签到,无法取消');
    let activity = await ActivityModel.getOne(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID);    if (!activity)      this.AppError('该活动不存在');
    if (activity.ACTIVITY_END <= this._timestamp)      this.AppError('该活动已经结束,无法取消');
    if (activity.ACTIVITY_CANCEL_SET == 0)      this.AppError('该活动不能取消');
    if (activity.ACTIVITY_CANCEL_SET == 2 && activity.ACTIVITY_STOP < this._timestamp)      this.AppError('该活动已经截止报名,不能取消');
    await ActivityJoinModel.del(where); 
    // 统计    await this.statActivityJoin(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID);  }
  /** 用户自助签到 */  async myJoinSelf(userId, activityId) {    let activity = await ActivityModel.getOne(activityId);    if (!activity)      this.AppError('活动不存在或者已经关闭');
    let day = timeUtil.timestamp2Time(activity.ACTIVITY_START, 'Y-M-D');
    let today = timeUtil.time('Y-M-D');    if (day != today)      this.AppError('仅在活动当天可以签到,当前签到码的日期是' + day);
    let whereSucc = {      ACTIVITY_JOIN_USER_ID: userId,      ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC    }    let cntSucc = await ActivityJoinModel.count(whereSucc);
    let whereCheckin = {      ACTIVITY_JOIN_USER_ID: userId,      ACTIVITY_JOIN_IS_CHECKIN: 1,      ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC    }    let cntCheckin = await ActivityJoinModel.count(whereCheckin);
    let ret = '';    if (cntSucc == 0) {      ret = '您没有本次活动报名成功的记录,请在「个人中心 - 我的活动报名」查看详情~';    } else if (cntSucc == cntCheckin) {      // 同一活动多次报名的情况      ret = '您已签到,无须重复签到,请在「个人中心 - 我的活动报名」查看详情~';    } else {      let where = {        ACTIVITY_JOIN_USER_ID: userId,        ACTIVITY_JOIN_IS_CHECKIN: 0,        ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC      }      let data = {        ACTIVITY_JOIN_IS_CHECKIN: 1,        ACTIVITY_JOIN_CHECKIN_TIME: this._timestamp,      }      await ActivityJoinModel.edit(where, data);      ret = '签到成功,请在「个人中心 - 我的活动报名」查看详情~'    }    return {      ret    };  }
  /** 按天获取报名项目 */  async getActivityListByDay(day) {    let start = timeUtil.time2Timestamp(day);    let end = start + 86400 * 1000 - 1;    let where = {      ACTIVITY_STATUS: ActivityModel.STATUS.COMM,      ACTIVITY_START: ['between', start, end],    };
    let orderBy = {      'ACTIVITY_ORDER': 'asc',      'ACTIVITY_ADD_TIME': 'desc'    };
    let fields = 'ACTIVITY_TITLE,ACTIVITY_START,ACTIVITY_OBJ.cover';
    let list = await ActivityModel.getAll(where, fields, orderBy);
    let retList = [];
    for (let k = 0; k < list.length; k++) {
      let node = {};      node.timeDesc = timeUtil.timestamp2Time(list[k].ACTIVITY_START, 'h:m');      node.title = list[k].ACTIVITY_TITLE;      node.pic = list[k].ACTIVITY_OBJ.cover[0];      node._id = list[k]._id;      retList.push(node);
    }    return retList;  }
  /**   * 获取从某天开始可报名的日期   * @param {*} fromDay  日期 Y-M-D   */  async getActivityHasDaysFromDay(fromDay) {    let where = {      ACTIVITY_START: ['>=', timeUtil.time2Timestamp(fromDay)],    };
    let fields = 'ACTIVITY_START';    let list = await ActivityModel.getAllBig(where, fields);
    let retList = [];    for (let k = 0; k < list.length; k++) {      let day = timeUtil.timestamp2Time(list[k].ACTIVITY_START, 'Y-M-D');      if (!retList.includes(day)) retList.push(day);    }    return retList;  }
}
复制代码
 UI 设计
 
  
  
  
  
  
  
  
  
  
  
  
 后台管理系统设计
 
  
  
  
  
  
  
  
  
  
 git 代码库
划线
评论
复制
发布于: 刚刚阅读数: 2

CC同学
关注
CC同学的小程序开发笔记 2021-06-13 加入
大鹅厂的小小程序媛,vx: cclinux0730







 
    
 
				 
				 
			


评论