可研剖析
大学生流动社交小程序是一种基于挪动互联网的社交平台,旨在为大学生提供一个不便、快捷、平安的社交和流动交流平台
性能布局
- 流动公布:平台能够公布将要举办的流动,包含工夫、地点、费用等信息,并邀请其余用户加入。
- 流动搜寻:用户能够依据本人的兴趣爱好搜寻流动,并加入感兴趣的流动。
- 流动评估:用户能够对加入过的流动进行评估和反馈,帮忙其余用户更好地抉择流动。
- 打卡模块:能够帮忙大学生记录和评比每天的学习、浏览、文艺、体育、体育等流动。通过实现一个个小指标帮忙大学生更好地治理本人的工夫和指标。
-
实现目标
大学生流动社交小程序能够帮忙大学生更好地组织和加入流动,扩大社交圈子,丰盛大学生存。本我的项目前后端残缺代码包含布告告诉,校园风采,流动分类与列表,流动报名与评估,打卡我的项目列表,打卡排行与每日动静,我的流动报名,我的每日打卡;后盾打卡项目管理,打卡记录治理与导出,后盾流动与报名治理,报名审核与数据导出等性能,流动组织方能够自定义要填写的内容
数据库设计
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',
};
外围实现
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_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 activityJoin(userId, activityId, forms) {
// 报名是否完结
let whereActivity = {
_id: activityId,
ACTIVITY_STATUS: ActivityModel.STATUS.COMM
}
let activity = await ActivityModel.getOne(whereActivity);
if (!activity)
this.AppError('该流动不存在或者曾经进行');
// 是否流动完结
if (activity.ACTIVITY_END < this._timestamp)
this.AppError('该流动曾经完结,请抉择其余流动');
// 是否过了报名截止期
if (activity.ACTIVITY_STOP < this._timestamp)
this.AppError('该流动报名曾经截止,请抉择其余流动');
// 人数是否满
if (activity.ACTIVITY_MAX_CNT > 0) {
let whereCnt = {
ACTIVITY_JOIN_ACTIVITY_ID: activityId,
ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]
}
let cntJoin = await ActivityJoinModel.count(whereCnt);
if (cntJoin >= activity.ACTIVITY_MAX_CNT)
this.AppError('该流动报名已满,请抉择其余流动');
}
// 本人是否曾经有报名
let whereMy = {
ACTIVITY_JOIN_USER_ID: userId,
ACTIVITY_JOIN_ACTIVITY_ID: activityId,
ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]
}
let my = await ActivityJoinModel.getOne(whereMy);
if (my) {if (my.ACTIVITY_JOIN_STATUS == ActivityJoinModel.STATUS.WAIT)
this.AppError('您曾经报名,正在期待审核,毋庸反复报名');
else
this.AppError('您曾经报名胜利,毋庸反复报名');
}
// 入库
let data = {
ACTIVITY_JOIN_USER_ID: userId,
ACTIVITY_JOIN_ACTIVITY_ID: activityId,
ACTIVITY_JOIN_STATUS: (activity.ACTIVITY_CHECK_SET == 0) ? ActivityJoinModel.STATUS.SUCC : ActivityJoinModel.STATUS.WAIT,
ACTIVITY_JOIN_FORMS: forms,
ACTIVITY_JOIN_OBJ: dataUtil.dbForms2Obj(forms),
ACTIVITY_JOIN_CODE: dataUtil.genRandomIntString(15),
}
let activityJoinId = await ActivityJoinModel.insert(data);
// 统计数量
await this.statActivityJoin(activityId);
let check = activity.ACTIVITY_CHECK_SET;
return {activityJoinId, check}
}
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 设计
后盾 UI
git 地址
git 代码地址