开发背景
随着全民健身的遍及,为进一步贯彻落实《“衰弱中国 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 源码下载