背景分析

书籍是人类进步的阶梯,在信息高度发达而浮躁的当下,静下心来读书成为都市人群的新趋势, 很多读书社团应运而生,那么通过设计一款小程序,把读书会搬到手机上,通过小程序了解最热门的书单和作家,可以每天组织阅读打卡和评比;书友会时常组织书友进行线下活动,那么通过小程序可以方便的搞定报名、签到、活动信息收集,活动结束后还可以进行评价。

功能规划

数据库设计


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_START: 'int|false|comment=开始时间',
    ENROLL_END: 'int|false|comment=结束时间',
    ENROLL_DAY_CNT: 'int|false|comment=持续天数',

    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=[]',

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

    ENROLL_USER_LIST: 'array|true|default=[]|comment={name,id,pic}',

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

EnrollJoinModel.DB_STRUCTURE = {
    _pid: 'string|true',
    ENROLL_JOIN_ID: 'string|true',
    ENROLL_JOIN_ENROLL_ID: 'string|true|comment=打卡PK',

    ENROLL_JOIN_USER_ID: 'string|true|comment=用户ID',
    ENROLL_JOIN_DAY: 'string|true|comment=日期',
    ENROLL_JOIN_FORMS: 'array|true|default=[]|comment=表单',

    ENROLL_JOIN_STATUS: 'int|true|default=1|comment=状态 1=成功', 

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

核心实现


class EnrollService extends BaseProjectService {

    // 获取当前打卡状态
    getJoinStatusDesc(enroll) {
        let timestamp = this._timestamp;

        if (enroll.ENROLL_STATUS == 0)
            return '已停止';
        else if (enroll.ENROLL_START > timestamp)
            return '未开始';
        else if (enroll.ENROLL_END <= timestamp)
            return '已结束';
        else
            return '进行中';
    }

    // 获取某日动态
    async getEnrollJoinByDay(enrollId, day = '') {
        if (!day) day = timeUtil.time('Y-M-D');

        let where = {
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_DAY: day,
            ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC
        }
        let joinParams = {
            from: UserModel.CL,
            localField: 'ENROLL_JOIN_USER_ID',
            foreignField: 'USER_MINI_OPENID',
            as: 'user',
        };
        let orderBy = {
            ENROLL_JOIN_ADD_TIME: 'desc'
        }
        let list = await EnrollJoinModel.getListJoin(joinParams, where, 'ENROLL_JOIN_ADD_TIME,user.USER_NAME,user.USER_PIC', orderBy, 1, 100, false, 0);
        return list.list;
    }

    // 获取某活动排行
    async getEnrollUserRank(enrollId) {

        let where = {
            ENROLL_USER_ENROLL_ID: enrollId
        }
        let joinParams = {
            from: UserModel.CL,
            localField: 'ENROLL_USER_MINI_OPENID',
            foreignField: 'USER_MINI_OPENID',
            as: 'user',
        };
        let orderBy = {
            ENROLL_USER_JOIN_CNT: 'desc'
        }
        let fields = 'ENROLL_USER_JOIN_CNT,ENROLL_USER_LAST_DAY,user.USER_NAME,user.USER_PIC';
        let list = await EnrollUserModel.getListJoin(joinParams, where, fields, orderBy, 1, 100, false, 0);
        return list.list;
    }

    /** 浏览信息 */
    async viewEnroll(userId, id) {

        let fields = '*';

        let where = {
            _id: id,
            ENROLL_STATUS: EnrollModel.STATUS.COMM
        }
        let enroll = await EnrollModel.getOne(where, fields);
        if (!enroll) return null;

        EnrollModel.inc(id, 'ENROLL_VIEW_CNT', 1);

        // 判断用户今日是否有打卡
        let whereJoin = {
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: id,
            ENROLL_JOIN_DAY: timeUtil.time('Y-M-D'),
            ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC
        }
        let enrollJoin = await EnrollJoinModel.getOne(whereJoin);
        if (enrollJoin) {
            enroll.myEnrollJoinId = enrollJoin._id;
        }
        else {
            enroll.myEnrollJoinId = '';
        }

        // 某日打卡列表
        enroll.activity = await this.getEnrollJoinByDay(id);

        // 打卡日期数组
        let dayList = [];
        let start = timeUtil.timestamp2Time(enroll.ENROLL_START, 'Y-M-D');
        start = timeUtil.time2Timestamp(start);
        let today = timeUtil.time2Timestamp(timeUtil.time('Y-M-D'));

        for (let k = start; k <= today;) {
            let month = timeUtil.timestamp2Time(k, 'M月');
            if (month.startsWith('0')) month = month.substring(1);

            let date = timeUtil.timestamp2Time(k, 'D');
            let day = timeUtil.timestamp2Time(k, 'Y-M-D');

            dayList.push({ month, date, day });
            k = k + 86400 * 1000;
        }
        enroll.dayList = dayList;

        // 排行榜 
        let rankList = await this.getEnrollUserRank(id);
        enroll.rankList = rankList;

        return enroll;
    }


    /** 取得分页列表 */
    async getEnrollList({
        search, // 搜索条件
        sortType, // 搜索菜单
        sortVal, // 搜索菜单
        orderBy, // 排序 
        page,
        size,
        isTotal = true,
        oldTotal
    }) {

        orderBy = orderBy || {
            'ENROLL_ORDER': 'asc',
            'ENROLL_ADD_TIME': 'desc'
        };
        let fields = 'ENROLL_USER_LIST,ENROLL_JOIN_CNT,ENROLL_OBJ,ENROLL_USER_CNT,ENROLL_TITLE,ENROLL_START,ENROLL_END,ENROLL_ORDER,ENROLL_STATUS,ENROLL_CATE_NAME,ENROLL_OBJ';

        let where = {};
        where.and = {
            _pid: this.getProjectId() //复杂的查询在此处标注PID
        };

        where.and.ENROLL_STATUS = EnrollModel.STATUS.COMM; // 状态  

        if (util.isDefined(search) && search) {
            where.or = [{
                ENROLL_TITLE: ['like', search]
            },];
        } else if (sortType && util.isDefined(sortVal)) {
            // 搜索菜单
            switch (sortType) {
                case 'cateId': {
                    if (sortVal) where.and.ENROLL_CATE_ID = String(sortVal);
                    break;
                }
                case 'sort': {
                    orderBy = this.fmtOrderBySort(sortVal, 'ENROLL_ADD_TIME');
                    break;
                }
                case 'today': { //今天
                    let day = timeUtil.time('Y-M-D');
                    where.and.ENROLL_DAYS = day;
                    break;
                }
                case 'tomorrow': { //明日
                    let day = timeUtil.time('Y-M-D', 86400);
                    where.and.ENROLL_DAYS = day;
                    break;
                }
                case 'yesterday': { //昨天 
                    let day = timeUtil.time('Y-M-D', -86400);
                    where.and.ENROLL_DAYS = day;
                    break;
                }
                case 'month': { //本月 
                    let day = timeUtil.time('Y-M-D');
                    let start = timeUtil.getMonthFirstTimestamp(day);
                    let end = timeUtil.getMonthLastTimestamp(day);
                    start = timeUtil.timestamp2Time(start, 'Y-M-D');
                    end = timeUtil.timestamp2Time(end, 'Y-M-D'); 
                    where.and.ENROLL_DAYS = ['between', start, end];
                    break;
                }

            }
        }

        return await EnrollModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);
    }

    /** 取得我的打卡分页列表 */
    async getMyEnrollUserList(userId, {
        search, // 搜索条件
        sortType, // 搜索菜单
        sortVal, // 搜索菜单
        orderBy, // 排序 
        page,
        size,
        isTotal = true,
        oldTotal
    }) {
        orderBy = orderBy || {
            'ENROLL_USER_ADD_TIME': 'asc'
        };
        let fields = 'ENROLL_USER_LAST_DAY,ENROLL_USER_ENROLL_ID,ENROLL_USER_JOIN_CNT,enroll.ENROLL_TITLE,enroll.ENROLL_OBJ.cover,enroll.ENROLL_USER_CNT,enroll.ENROLL_CATE_NAME,enroll.ENROLL_DAY_CNT,enroll.ENROLL_START,enroll.ENROLL_END,enroll.ENROLL_STATUS';

        let where = {
            ENROLL_USER_MINI_OPENID: userId
        };

        if (util.isDefined(search) && search) {
            where['enroll.ENROLL_TITLE'] = {
                $regex: '.*' + search,
                $options: 'i'
            };
        } else if (sortType) {
            // 搜索菜单
            let timestamp = this._timestamp;

            switch (sortType) {
                case 'stop': {
                    where['enroll.ENROLL_STATUS'] = 0;
                    break;
                }
                case 'un': {
                    where['enroll.ENROLL_START'] = ['>', timestamp];
                    break;
                }
                case 'over': {
                    where['enroll.ENROLL_END'] = ['<=', timestamp];
                    break;
                }
                case 'run': {
                    where['enroll.ENROLL_STATUS'] = 1;
                    where['enroll.ENROLL_START'] = ['<=', timestamp];
                    where['enroll.ENROLL_END'] = ['>', timestamp];
                    break;
                }

            }
        }

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

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

        let list = result.list;
        for (let k = 0; k < list.length; k++) {
            let enroll = {
                ENROLL_START: list[k].enroll.ENROLL_START,
                ENROLL_END: list[k].enroll.ENROLL_END,
                ENROLL_STATUS: list[k].enroll.ENROLL_STATUS,
            }
            let status = this.getJoinStatusDesc(enroll);

            if (status == '进行中') {
                if (list[k].ENROLL_USER_LAST_DAY == timeUtil.time('Y-M-D'))
                    status = '已打卡';
            }
            list[k].status = status;

            list[k].last = list[k].ENROLL_USER_LAST_DAY.split('-')[1] + '-' + list[k].ENROLL_USER_LAST_DAY.split('-')[2];
        }

        return result;
    }

    /** 取得我的打卡清单列表 */
    async getMyEnrollJoinList(userId, {
        enrollId,
        search, // 搜索条件
        sortType, // 搜索菜单
        sortVal, // 搜索菜单
        orderBy, // 排序 
        page,
        size,
        isTotal = true,
        oldTotal
    }) {
        orderBy = orderBy || {
            'ENROLL_JOIN_ADD_TIME': 'desc'
        };
        let fields = '*';

        let where = {
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: enrollId
        };

        return await EnrollJoinModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);
    }


    //################## 打卡 
    addEnrollUserList(userList, user) {

        //查询是否存在 并删除
        for (let k = 0; k < userList.length; k++) {
            if (userList[k].id == user.id)
                userList.splice(k, 1);
        }

        userList.unshift(user);

        // 判断个数, 多的删除
        if (userList.length > 3)
            userList.splice(userList.length - 1, 1);

        return userList;
    }

    // 打卡 
    async enrollJoin(userId, enrollId, forms) {

        let user = await UserModel.getOne({ USER_MINI_OPENID: userId, USER_STATUS: UserModel.STATUS.COMM });
        if (!user) this.AppError('用户不存在');

        // 打卡是否结束
        let whereEnroll = {
            _id: enrollId,
            ENROLL_STATUS: EnrollModel.STATUS.COMM
        }
        let enroll = await EnrollModel.getOne(whereEnroll);
        if (!enroll)
            this.AppError('该打卡活动不存在或者已经停止');

        // 是否打卡开始
        if (enroll.ENROLL_START > this._timestamp)
            this.AppError('该打卡活动尚未开始');

        // 是否过了打卡结束期
        if (enroll.ENROLL_END < this._timestamp)
            this.AppError('该打卡活动已经结束');

        let day = timeUtil.time('Y-M-D');

        // 自己今日是否已经有打卡
        let whereMy = {
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_DAY: day,
            ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC
        }
        let my = await EnrollJoinModel.getOne(whereMy);
        if (my) {
            this.AppError('您今日已打卡,无须重复打卡');
        }

        // 入库
        let data = {
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC,
            ENROLL_JOIN_DAY: day,
            ENROLL_JOIN_FORMS: forms
        }

        let enrollJoinId = await EnrollJoinModel.insert(data);

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

        // 更新用户头像 
        let userList = enroll.ENROLL_USER_LIST;
        userList = this.addEnrollUserList(userList, { pic: user.USER_PIC, id: userId, name: user.USER_NAME });
        EnrollModel.edit(enrollId, { ENROLL_USER_LIST: userList });

        return { enrollJoinId }

    }

    // 统计
    async statEnrollJoin(enrollId, userId = '', del = false) {
        // 总体统计
        let where = {
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC
        }
        let joinCnt = await EnrollJoinModel.count(where);
        let userCnt = await EnrollJoinModel.distinctCnt(where, 'ENROLL_JOIN_USER_ID');

        let data = {
            ENROLL_JOIN_CNT: joinCnt,
            ENROLL_USER_CNT: userCnt,
        }
        await EnrollModel.edit(enrollId, data);

        // 用户统计
        if (!userId) return;
        where = {
            ENROLL_JOIN_USER_ID: userId,
            ENROLL_JOIN_ENROLL_ID: enrollId,
            ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC
        }
        let userJoinCnt = await EnrollJoinModel.count(where);


        let enrollUserData = {};
        enrollUserData.ENROLL_USER_LAST_DAY = timeUtil.time('Y-M-D');
        enrollUserData.ENROLL_USER_JOIN_CNT = userJoinCnt;
        enrollUserData.ENROLL_USER_DAY_CNT = userJoinCnt;

        where = {
            ENROLL_USER_MINI_OPENID: userId,
            ENROLL_USER_ENROLL_ID: enrollId
        };
        await EnrollUserModel.insertOrUpdate(where, enrollUserData);

        if (del) {
            //删除打卡记录,则更新最近打卡时间
            let last = '';
            where = {
                ENROLL_JOIN_USER_ID: userId,
                ENROLL_JOIN_ENROLL_ID: enrollId,
                ENROLL_JOIN_STATUS: EnrollJoinModel.STATUS.SUCC
            }
            let lastModel = await EnrollJoinModel.getOne(where, 'ENROLL_JOIN_DAY', { 'ENROLL_JOIN_ADD_TIME': 'desc' });
            if (lastModel) last = lastModel.ENROLL_JOIN_DAY;

            where = {
                ENROLL_USER_MINI_OPENID: userId,
                ENROLL_USER_ENROLL_ID: enrollId
            };
            await EnrollUserModel.edit(where, { ENROLL_USER_LAST_DAY: last });
        }

    }

}

UI设计

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

后端UI设计

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

代码

git代码地址


CC同学呀
27 声望13 粉丝

鹅厂程序猿一枚,交流v: cclinux0730