乐趣区

关于小程序:从0到1学员课时预约与扣课小程序开发笔记

开发背景

机构:各类音体美,数理化培训机构须要进行课时治理、课时统计、课时计算、课时记录、上课预约注销,
学员:须要实时查看本人的课程耗费状况,应用记录。
管理者:须要随时查看,增减学员的课程数量

性能布局

技术选型

  • 应用微信小程序平台进行开发。
  • 应用腾讯专门的小程序云开发技术,云资源蕴含云函数,数据库,带宽,存储空间,定时器等,资源配额价格低廉,无需域名和服务器即可搭建。
  • 小程序自身的即用即走,适宜小工具的应用场景,也适宜疾速开发迭代。
  • 云开发技术采纳腾讯外部链路,没有被黑客攻击的危险,不会 DDOS 攻打,节俭防火墙费用,安全性高且免保护。
  • 资源承载力可依据业务倒退须要随时弹性扩大

数据库设计

LessonLogModel.DB_STRUCTURE = {
    _pid: 'string|true',
    LESSON_LOG_ID: 'string|true',


    LESSON_LOG_USER_ID: 'string|true|comment= 用户 ID',

    LESSON_LOG_MEET_ID: 'string|false|comment= 预约我的项目 PK',

    LESSON_LOG_DESC: 'string|false|comment= 备注',

    LESSON_LOG_TYPE: 'int|true|default=1|comment= 类型 0= 用户约课 -,1= 用户勾销预约 +,10= 后盾减少课时 +,11= 后盾缩小课时 -,12= 后盾勾销预约 +,13= 后盾复原 +',

    LESSON_LOG_EDIT_ADMIN_ID: 'string|false|comment= 最近批改的管理员 ID',
    LESSON_LOG_EDIT_ADMIN_NAME: 'string|false|comment= 最近批改的管理员名',
    LESSON_LOG_EDIT_ADMIN_TIME: 'int|true|default=0|comment= 管理员最近批改的工夫',


    LESSON_LOG_CHANGE_CNT: 'int|true|default=0|comment= 当变动课时数 (可正负)',
    LESSON_LOG_LAST_CNT: 'int|true|default=0|comment= 变动前次数',
    LESSON_LOG_NOW_CNT: 'int|true|default=0|comment= 以后次数', 

    LESSON_LOG_ADD_TIME: 'int|true',
    LESSON_LOG_ADD_IP: 'string|false',

    LESSON_LOG_EDIT_TIME: 'int|true',
    LESSON_LOG_EDIT_IP: 'string|false',
}

// 字段前缀
LessonLogModel.FIELD_PREFIX = "LESSON_LOG_";

/**
 * 类型 0= 初始赠送,1= 学员约课,2= 学员勾销预约,10= 后盾减少课时,11= 后盾缩小课时,12= 后盾勾销预约,13= 后盾复原
 */
LessonLogModel.TYPE = {
    INIT: 0,
    USER_APPT: 1,
    USER_CANCEL: 2,
    ADMIN_ADD: 10,
    ADMIN_REDUCE: 11,
    ADMIN_CANCEL: 12,
    ADMIN_RECOVER: 13
};

LessonLogModel.TYPE_DESC = {
    INIT: '初始赠送',
    USER_APPT: '学员约课',
    USER_CANCEL: '学员勾销预约',
    ADMIN_ADD: '后盾减少课时',
    ADMIN_REDUCE: '后盾缩小课时',
    ADMIN_CANCEL: '后盾勾销预约',
    ADMIN_RECOVER: '后盾复原预约'
};

JoinModel.DB_STRUCTURE = {
    _pid: 'string|true',
    JOIN_ID: 'string|true',

    JOIN_EDIT_ADMIN_ID: 'string|false|comment= 最近批改的管理员 ID',
    JOIN_EDIT_ADMIN_NAME: 'string|false|comment= 最近批改的管理员名',
    JOIN_EDIT_ADMIN_TIME: 'int|true|default=0|comment= 管理员最近批改的工夫',
    JOIN_EDIT_ADMIN_STATUS: 'int|false|comment= 最近管理员批改为的状态',

    JOIN_IS_ADMIN: 'int|true|default=0|comment= 是否管理员增加 0/1',

    JOIN_CODE: 'string|true|comment= 核验码 15 位',
    JOIN_IS_CHECKIN: 'int|true|default=0|comment= 是否核销 0/1',
    JOIN_CHECKIN_TIME: 'int|true|default=0',

    JOIN_USER_ID: 'string|true|comment= 用户 ID',
    JOIN_MEET_ID: 'string|true|comment= 预约 PK',
    JOIN_MEET_CATE_ID: 'string|true',
    JOIN_MEET_CATE_NAME: 'string|true',
    JOIN_MEET_TITLE: 'string|true|comment= 我的项目',
    JOIN_MEET_DAY: 'string|true|comment= 日期',
    JOIN_MEET_TIME_START: 'string|true|comment= 时段开始',
    JOIN_MEET_TIME_END: 'string|true|comment= 时段完结',
    JOIN_MEET_TIME_MARK: 'string|true|comment= 时段标识',

    JOIN_COMPLETE_END_TIME: 'string|false|comment= 残缺完结工夫',

    JOIN_START_TIME: 'int|true|comment= 开始工夫戳',

    JOIN_FORMS: 'array|true|default=[]|comment= 表单',
    /* title:
       mark:
       type:
       val:
    */
    JOIN_OBJ: 'object|true|default={}',

    JOIN_STATUS: 'int|true|default=1|comment= 状态 1= 预约胜利,10= 已勾销, 99= 零碎勾销',

    JOIN_REASON: 'string|false|comment= 审核回绝或者勾销理由',

    JOIN_ADD_TIME: 'int|true',
    JOIN_EDIT_TIME: 'int|true',
    JOIN_ADD_IP: 'string|false',
    JOIN_EDIT_IP: 'string|false',
};

要害代码实现

// 用户预约逻辑
    async join(userId, meetId, timeMark, formsList) {
        // 预约时段是否存在
        let meetWhere = {_id: meetId};
        let day = this.getDayByTimeMark(timeMark);
        let meet = await this.getMeetOneDay(meetId, day, meetWhere);

        if (!meet) {this.AppError('预约时段抉择谬误 1,请从新抉择');
        }

        let daySet = this.getDaySetByTimeMark(meet, timeMark);
        if (!daySet)
            this.AppError('预约时段抉择谬误 2,请从新抉择');

        let timeSet = this.getTimeSetByTimeMark(meet, timeMark);
        if (!timeSet)
            this.AppError('预约时段抉择谬误 3,请从新抉择');

        // 规定校验
        await this.checkMeetRules(userId, meetId, timeMark, formsList);


        let data = {};

        data.JOIN_USER_ID = userId;
        data.JOIN_MEET_ID = meetId;
        data.JOIN_MEET_CATE_ID = meet.MEET_CATE_ID;
        data.JOIN_MEET_CATE_NAME = meet.MEET_CATE_NAME;
        data.JOIN_MEET_TITLE = meet.MEET_TITLE;
        data.JOIN_MEET_DAY = daySet.day;
        data.JOIN_MEET_TIME_START = timeSet.start;
        data.JOIN_MEET_TIME_END = timeSet.end;
        data.JOIN_MEET_TIME_MARK = timeMark;
        data.JOIN_START_TIME = timeUtil.time2Timestamp(daySet.day + '' + timeSet.start +':00');
        data.JOIN_STATUS = JoinModel.STATUS.SUCC;
        data.JOIN_COMPLETE_END_TIME = daySet.day + ' ' + timeSet.end;

        // 入库
        for (let k = 0; k < formsList.length; k++) {let forms = formsList[k];
            data.JOIN_FORMS = forms;
            data.JOIN_OBJ = dataUtil.dbForms2Obj(forms);
            data.JOIN_CODE = dataUtil.genRandomIntString(15);
            await JoinModel.insert(data);
        }


        // 统计
        await this.statJoinCnt(meetId, timeMark);

        // 课时统计
        await this.editUserMeetLesson(null, userId, -1, LessonLogModel.TYPE.USER_APPT, meetId, '《' + meet.MEET_TITLE + '》')

        return {result: 'ok',}
    }

前端 UI 设计

老师端

后盾治理端

git 源码

git 源码下载

退出移动版