乐趣区

关于小程序:从0到100中小学运动体育设施开放预约小程序

开发背景

随着全民健身的遍及,为进一步贯彻落实《“衰弱中国 2030”布局大纲》和《全民健身条例》,更好地满足宽广青少年学生和人民大众就近、便当加入体育健身活动的需要,继续扩充公共服务配套资源供应,中小学校园的操场,静止设施周也开始对市民凋谢,那么为了正当工夫,不便各项设施的无效利用,缩小不必要的人员汇集,预订小程序提供了线上预约的便捷性

性能概要设计

技术选型

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

数据库设计


EnrollJoinModel.DB_STRUCTURE = {
    _pid: 'string|true',
    ENROLL_JOIN_ID: 'string|true',
    ENROLL_JOIN_PRICE: 'int|true|default=0',

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

    ENROLL_JOIN_ENROLL_ID: 'string|true|comment= 报名 PK', 
    ENROLL_JOIN_ENROLL_TITLE: 'string|false', 
    ENROLL_JOIN_CATE_ID: 'string|false|default=0|comment= 分类',
    ENROLL_JOIN_CATE_NAME: 'string|false|comment= 分类冗余',


    ENROLL_JOIN_CODE: 'string|true|comment= 核验码 15 位',
    ENROLL_JOIN_IS_CHECKIN: 'int|true|default=0|comment= 是否核销 0/1',
    ENROLL_JOIN_CHECKIN_TIME: 'int|true|default=0',

    ENROLL_JOIN_DAY: 'string|false|comment= 日期',
    ENROLL_JOIN_START: 'string|false|comment= 开始工夫',
    ENROLL_JOIN_END: 'string|false|comment= 完结工夫',
    ENROLL_JOIN_END_POINT: 'string|false|comment= 完结工夫开端',

    ENROLL_JOIN_END_FULL: 'string|false|comment= 残缺的完结工夫 YYYY-MM-DD hh:mm',
    ENROLL_JOIN_START_FULL: 'string|false|comment= 残缺的开始工夫 YYYY-MM-DD hh:mm',

    ENROLL_JOIN_USER_ID: 'string|true|comment= 用户 ID',
    ENROLL_JOIN_FORMS: 'array|true|default=[]|comment= 表单',
    ENROLL_JOIN_OBJ: 'object|true|default={}',

    ENROLL_JOIN_STATUS: 'int|true|default=1|comment= 状态 0= 待审核 1= 胜利, 9= 用户勾销, 99= 零碎勾销',

    ENROLL_JOIN_LAST_TIME: 'int|true|default=0',

    ENROLL_JOIN_ADD_TIME: 'int|true',
    ENROLL_JOIN_EDIT_TIME: 'int|true',
    ENROLL_JOIN_ADD_IP: 'string|false',
    ENROLL_JOIN_EDIT_IP: 'string|false',
};

// 字段前缀
EnrollJoinModel.FIELD_PREFIX = "ENROLL_JOIN_";

/**
 * 状态 0= 待审核 1= 胜利,9= 用户勾销, 99= 审核未过 
 */
EnrollJoinModel.STATUS = {
    WAIT: 0,
    SUCC: 1,
    CANCEL: 9,
    ADMIN_CANCEL: 99
};

EnrollJoinModel.STATUS_DESC = {
    WAIT: '待审核',
    SUCC: '胜利',
    CANCEL: '用户勾销',
    ADMIN_CANCEL: '零碎勾销'
};

EnrollModel.DB_STRUCTURE = {
    _pid: 'string|true',
    ENROLL_ID: 'string|true',

    ENROLL_TITLE: 'string|true|comment= 题目',
    ENROLL_STATUS: 'int|true|default=1|comment= 状态 0= 未启用,1= 应用中',

    ENROLL_CATE_ID: 'string|true|default=0|comment= 分类',
    ENROLL_CATE_NAME: 'string|false|comment= 分类冗余',

    ENROLL_CANCEL_SET: 'int|true|default=1|comment= 勾销设置 0= 不允,1= 容许,2= 开始前可勾销,3= 完结前可勾销',
    ENROLL_EDIT_SET: 'int|true|default=1|comment= 批改 0= 不允,1= 容许,2= 开始前可批改,3= 完结前可批改', 

    ENROLL_ORDER: 'int|true|default=9999',
    ENROLL_VOUCH: 'int|true|default=0',

    ENROLL_FORMS: 'array|true|default=[]',
    ENROLL_OBJ: 'object|true|default={}',

    ENROLL_JOIN_FORMS: 'array|true|default=[]',

    ENROLL_DAYS: 'array|true|default=[]|comment= 最近一次批改保留的可用日期',
    ENROLL_DAY_CNT: 'int|true|default=0',

    ENROLL_QR: 'string|false',
    ENROLL_VIEW_CNT: 'int|true|default=0',
    ENROLL_JOIN_CNT: 'int|true|default=0',

    ENROLL_ADD_TIME: 'int|true',
    ENROLL_EDIT_TIME: 'int|true',
    ENROLL_ADD_IP: 'string|false',
    ENROLL_EDIT_IP: 'string|false',
};

要害难点



    // 获取某天某个场合下的可约工夫点
    async getOneDayTimePoint(day, enrollId) {
        let where = {
            DAY_ENROLL_ID: enrollId,
            day
        };
        let fields = 'times';
        let data = await DayModel.getOne(where, fields);
        if (!data)
            data = [];
        else
            data = data.times;

        return data;

    }

    // 获得某天内所有场地信息
    async getAllEnroll(cateId, day) {

        let where = {ENROLL_CATE_ID: String(cateId),
            ENROLL_STATUS: EnrollModel.STATUS.COMM
        }
        let orderBy = {
            ENROLL_ORDER: 'asc',
            ENROLL_ADD_TIME: 'asc'
        }
        let list = await EnrollModel.getAll(where, '*', orderBy);
        let arr = [];

        let startTime = 23;
        let endTime = 0;

        for (let k = 0; k < list.length; k++) {let times = await this.getOneDayTimePoint(day, list[k]._id);

            // 合成小时
            let t = [];
            for (let j = 0; j < times.length; j++) {if (times[j].start < startTime) startTime = times[j].start;
                if (times[j].end > endTime) endTime = times[j].end;

                for (let i = times[j].start; i <= times[j].end; i++) {
                    let node = {
                        t: i, // 工夫点
                        price: times[j].price, // 价格
                    };
                    t.push(node);
                }
            }

            if (t.length > 0)
                arr.push({enrollId: list[k]._id,
                    label: list[k].ENROLL_TITLE,
                    timePrice: t
                })
        }


        // 获得可预订的最大日期
        let maxDay = await DayModel.max({day: ['>=', day], DAY_CATE_ID: cateId }, 'day');
        if (maxDay == 0) maxDay = '';

        return {
            maxDay,
            startTime,
            endTime,
            list: arr
        };
    }


    // 获取某天预订状况
    async getUsedByDay(cateId, day) {
        let where = {ENROLL_JOIN_CATE_ID: String(cateId),
            ENROLL_JOIN_DAY: day,
            ENROLL_JOIN_STATUS: ['in', [EnrollJoinModel.STATUS.WAIT, EnrollJoinModel.STATUS.SUCC]],
        };
        return EnrollJoinModel.getAll(where);
    }


    /** 获得我的注销分页列表 */
    async getMyEnrollJoinList(userId, {
        search, // 搜寻条件
        sortType, // 搜寻菜单
        sortVal, // 搜寻菜单
        orderBy, // 排序 
        page,
        size,
        isTotal = true,
        oldTotal
    }) {
        orderBy = orderBy || {'ENROLL_JOIN_ADD_TIME': 'desc'};
        let fields = 'ENROLL_JOIN_IS_CHECKIN,ENROLL_JOIN_CATE_NAME,ENROLL_JOIN_ENROL_TITLE,ENROLL_JOIN_PRICE,ENROLL_JOIN_END_FULL,ENROLL_JOIN_OBJ,ENROLL_JOIN_DAY,ENROLL_JOIN_START,ENROLL_JOIN_END,ENROLL_JOIN_END_POINT,ENROLL_JOIN_LAST_TIME,ENROLL_JOIN_ENROLL_ID,ENROLL_JOIN_STATUS,ENROLL_JOIN_ADD_TIME,enroll.ENROLL_TITLE,enroll.ENROLL_EDIT_SET,enroll.ENROLL_CANCEL_SET';

        let where = {ENROLL_JOIN_USER_ID: userId};

        if (util.isDefined(search) && search) {where['ENROLL_JOIN_OBJ.name'] = {
                $regex: '.*' + search,
                $options: 'i'
            };
        } else if (sortType) {
            // 搜寻菜单
            switch (sortType) {
                case 'timedesc': { // 按工夫倒序
                    orderBy = {'ENROLL_JOIN_START_FULL': 'desc'};
                    break;
                }
                case 'timeasc': { // 按工夫正序
                    orderBy = {'ENROLL_JOIN_START_FULL': 'asc'};
                    break;
                }
                case 'status': {break;}
                case 'today': {where.ENROLL_JOIN_DAY = timeUtil.time('Y-M-D');
                    where.ENROLL_JOIN_STATUS = EnrollJoinModel.STATUS.SUCC;
                    break;
                }
                case 'run': {where.ENROLL_JOIN_END_FULL = ['>', timeUtil.time('Y-M-D h:m')];
                    where.ENROLL_JOIN_STATUS = EnrollJoinModel.STATUS.SUCC;
                    where.ENROLL_JOIN_IS_CHECKIN = 0;
                    break;
                }
                case 'check': {where.ENROLL_JOIN_END_FULL = ['>', timeUtil.time('Y-M-D h:m')];
                    where.ENROLL_JOIN_STATUS = EnrollJoinModel.STATUS.SUCC;
                    where.ENROLL_JOIN_IS_CHECKIN = 1;
                    break;
                }
                case 'out': {where.ENROLL_JOIN_END_FULL = ['<=', timeUtil.time('Y-M-D h:m')];
                    where.ENROLL_JOIN_STATUS = EnrollJoinModel.STATUS.SUCC;
                    break;
                }
                case 'cancel': {
                    where.ENROLL_JOIN_STATUS = EnrollJoinModel.STATUS.CANCEL;
                    break;
                }
                case 'syscancel': {
                    where.ENROLL_JOIN_STATUS = EnrollJoinModel.STATUS.ADMIN_CANCEL;
                    break;
                }
            }
        }

        let joinParams = {
            from: EnrollModel.CL,
            localField: 'ENROLL_JOIN_ENROLL_ID',
            foreignField: '_id',
            as: 'enroll',
        };

        let result = await EnrollJoinModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);

        return result;
    }

    /** 获得我的注销详情 */
    async getMyEnrollJoinDetail(enrollJoinId) {

        let fields = '*';

        let where = {_id: enrollJoinId};
        let enrollJoin = await EnrollJoinModel.getOne(where, fields);
        if (enrollJoin) {enrollJoin.enroll = await EnrollModel.getOne(enrollJoin.ENROLL_JOIN_ENROLL_ID, 'ENROLL_TITLE');
        }


        return enrollJoin;
    }

    //################## 注销 
    // 把工夫格局 'hh:mm' 转为数组 [1,2,3,4]
    getTimeArr(start, end) {start = start.replace(':00', '').trim();
        start = start.replace(':30', '').trim();
        start = Number(start);


        end = end.replace(':00', '');
        end = end.replace(':30', '').trim();
        end = Number(end);

        let ret = [];
        for (let k = start; k <= end; k++) {ret.push(k);
        }

        return ret;
    }

    // 注销 
    async enrollJoin(userId, {
        enrollId,
        price,
        start,
        end,
        endPoint,
        day,
        forms
    }) {

        // 注销是否完结
        let whereEnroll = {
            _id: enrollId,
            ENROLL_STATUS: EnrollModel.STATUS.COMM
        }
        let enroll = await EnrollModel.getOne(whereEnroll);
        if (!enroll)
            this.AppError('该' + ENROLL_NAME + '不存在或者曾经进行');


        // 判断是否曾经被约 (数组交加)
        let nowTimeArr = this.getTimeArr(start, end);
        let joinWhere = {
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_DAY: day,
            ENROLL_JOIN_STATUS: ['in', [EnrollJoinModel.STATUS.WAIT, EnrollJoinModel.STATUS.SUCC]],
        }
        let joinList = await EnrollJoinModel.getAll(joinWhere, 'ENROLL_JOIN_START,ENROLL_JOIN_END', { 'ENROLL_JOIN_START': 'asc'});
        for (let k = 0; k < joinList.length; k++) {let listTimeArr = this.getTimeArr(joinList[k].ENROLL_JOIN_START, joinList[k].ENROLL_JOIN_END);
            for (let j = 0; j < nowTimeArr.length; j++) {if (listTimeArr.includes(nowTimeArr[j])) {this.AppError(nowTimeArr[j] + '点曾经被预订,请从新抉择');
                }
            }
        }


        // 入库
        let data = {
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_CATE_ID: enroll.ENROLL_CATE_ID,
            ENROLL_JOIN_CATE_NAME: enroll.ENROLL_CATE_NAME,

            ENROLL_JOIN_CODE: dataUtil.genRandomIntString(15),

            ENROLL_JOIN_PRICE: price,
            ENROLL_JOIN_START: start,
            ENROLL_JOIN_END: end,
            ENROLL_JOIN_END_POINT: endPoint,
            ENROLL_JOIN_DAY: day,
            ENROLL_JOIN_ENROLL_TITLE: enroll.ENROLL_TITLE,

            ENROLL_JOIN_END_FULL: day + ' ' + endPoint,
            ENROLL_JOIN_START_FULL: day + ' ' + start,

            ENROLL_JOIN_FORMS: forms,
            ENROLL_JOIN_OBJ: dataUtil.dbForms2Obj(forms),
        }

        let enrollJoinId = await EnrollJoinModel.insert(data);

        // 统计数量
        this.statEnrollJoin(enrollId);


        return {enrollJoinId}

    }


    // 批改注销 
    async enrollJoinEdit(userId, enrollId, enrollJoinId, forms) {
        let whereJoin = {
            _id: enrollJoinId,
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_STATUS: ['in', [EnrollJoinModel.STATUS.WAIT, EnrollJoinModel.STATUS.SUCC]],
        }
        let enrollJoin = await EnrollJoinModel.getOne(whereJoin);
        if (!enrollJoin)
            this.AppError('该' + ENROLL_NAME + '记录不存在或者曾经被零碎勾销');

        // 注销是否完结
        let whereEnroll = {
            _id: enrollId,
            ENROLL_STATUS: EnrollModel.STATUS.COMM
        }
        let enroll = await EnrollModel.getOne(whereEnroll);
        if (!enroll)
            this.AppError('该' + ENROLL_NAME + '不存在或者曾经进行');


        if (enrollJoin.ENROLL_JOIN_IS_CHECKIN == 1)
            this.AppError('该预订已核销,不能批改');

        if (enroll.ENROLL_EDIT_SET == 0)
            this.AppError('该' + ENROLL_NAME + '不容许批改材料');


        if (enroll.ENROLL_EDIT_SET == 2 && enrollJoin.ENROLL_JOIN_START_FULL <= timeUtil.time('Y-M-D h:m'))
            this.AppError('该' + ENROLL_NAME + '曾经开始,不能批改材料');


        if (enroll.ENROLL_EDIT_SET == 3 && enrollJoin.ENROLL_JOIN_END_FULL <= timeUtil.time('Y-M-D h:m'))
            this.AppError('该' + ENROLL_NAME + '曾经完结,不能批改材料');


        let data = {
            ENROLL_JOIN_FORMS: forms,
            ENROLL_JOIN_OBJ: dataUtil.dbForms2Obj(forms),
            ENROLL_JOIN_LAST_TIME: this._timestamp,
        }
        await EnrollJoinModel.edit(whereJoin, data);

    }

    async statEnrollJoin(id) {
        let where = {
            ENROLL_JOIN_ENROLL_ID: id,
            ENROLL_JOIN_STATUS: ['in', [EnrollJoinModel.STATUS.WAIT, EnrollJoinModel.STATUS.SUCC]]
        }
        let cnt = await EnrollJoinModel.count(where);

        await EnrollModel.edit(id, { ENROLL_JOIN_CNT: cnt});
    }

    /**  注销前获取要害信息 */
    async detailForEnrollJoin(userId, enrollId, enrollJoinId = '') {
        let fields = 'ENROLL_JOIN_FORMS, ENROLL_TITLE, ENROLL_CATE_NAME';

        let where = {
            _id: enrollId,
            ENROLL_STATUS: EnrollModel.STATUS.COMM
        }
        let enroll = await EnrollModel.getOne(where, fields);
        if (!enroll)
            this.AppError('该' + ENROLL_NAME + '不存在');



        let joinMy = null;
        if (enrollJoinId) {
            // 编辑
            let whereMy = {
                ENROLL_JOIN_USER_ID: userId,
                _id: enrollJoinId
            }
            joinMy = await EnrollJoinModel.getOne(whereMy);
            enroll.join = {
                start: joinMy.ENROLL_JOIN_START,
                end: joinMy.ENROLL_JOIN_END,
                endPoint: joinMy.ENROLL_JOIN_END_POINT,
                day: joinMy.ENROLL_JOIN_DAY,
            }
        }
        else {
            // 取出自己最近一次的填写表单 

            let whereMy = {ENROLL_JOIN_USER_ID: userId,}
            let orderByMy = {ENROLL_JOIN_ADD_TIME: 'desc'}
            joinMy = await EnrollJoinModel.getOne(whereMy, 'ENROLL_JOIN_FORMS', orderByMy);
        }

        let myForms = joinMy ? joinMy.ENROLL_JOIN_FORMS : [];
        enroll.myForms = myForms;

        return enroll;
    }

    /** 勾销我的注销   */
    async cancelMyEnrollJoin(userId, enrollJoinId) {
        let where = {
            ENROLL_JOIN_USER_ID: userId,
            _id: enrollJoinId,
            ENROLL_JOIN_STATUS: ['in', [EnrollJoinModel.STATUS.WAIT, EnrollJoinModel.STATUS.SUCC]]
        };
        let enrollJoin = await EnrollJoinModel.getOne(where);

        if (!enrollJoin) {this.AppError('未找到可勾销的记录');
        }

        if (enrollJoin.ENROLL_JOIN_IS_CHECKIN == 1)
            this.AppError('该预订已核销,不能取消');

        let enroll = await EnrollModel.getOne(enrollJoin.ENROLL_JOIN_ENROLL_ID);
        if (!enroll)
            this.AppError('该' + ENROLL_NAME + '不存在');

        if (enroll.ENROLL_CANCEL_SET == 0)
            this.AppError('该' + ENROLL_NAME + '不能取消');


        if (enroll.ENROLL_CANCEL_SET == 2 && enrollJoin.ENROLL_JOIN_START_FULL <= timeUtil.time('Y-M-D h:m'))
            this.AppError('该' + ENROLL_NAME + '曾经开始,不能取消');


        if (enroll.ENROLL_CANCEL_SET == 3 && enrollJoin.ENROLL_JOIN_END_FULL <= timeUtil.time('Y-M-D h:m'))
            this.AppError('该' + ENROLL_NAME + '曾经完结,不能取消');

        if (enroll.ENROLL_CANCEL_SET > 20) {

            let step = enroll.ENROLL_CANCEL_SET - 20;
            let day = timeUtil.time2Timestamp(enrollJoin.ENROLL_JOIN_END_FULL + ':00') - step * 86400 * 1000;
            day = timeUtil.timestamp2Time(day, 'Y-M-D');
            let now = timeUtil.time('Y-M-D');
            if (now > day) this.AppError('仅开始前' + step + '天可勾销');
        }

        await EnrollJoinModel.edit(where,
            {
                ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.CANCEL,
                ENROLL_JOIN_IS_CHECKIN: 0
            });

        await this.statEnrollJoin(enrollJoin.ENROLL_JOIN_ENROLL_ID);


    }

前端 UI 设计

后盾管理系统 UI 设计

git 源码

git 源码下载

退出移动版