活动报名与缴费小程序开发笔记一

项目背景

活动报名与缴费小程序的开发背景主要源于以下几个因素:

  • 1.数字化时代的需求: 随着移动互联网和智能手机的普及,人们习惯使用手机进行各种活动。传统的纸质报名表格和线下缴费方式变得相对繁琐,而数字化报名与缴费小程序提供了更便捷的解决方案。
  • 2.提高效率和减少人力成本: 对于活动举办者来说,传统的报名方式需要大量人力去处理报名表格、确认报名信息以及核实支付情况。自动化的小程序可以大大提高工作效率,减少人力成本。
  • 3.提供更好的用户体验: 活动报名与缴费小程序可以提供更好的用户体验。用户可以随时随地浏览活动信息、完成报名和支付,无需前往实体场地或填写繁琐的纸质表格,从而提高了用户的满意度。
  • 4.安全性和可追溯性: 小程序通常具有安全性较高的支付系统,可以保障用户的支付安全。同时,所有的报名和支付记录都被数字化存储,方便活动举办者进行管理和核实。
  • 5.数据分析和营销需求: 数字化的报名与缴费系统可以收集大量的数据,包括参与人数、地域分布、支付方式偏好等。这些数据对于市场营销和活动策划非常有价值,可以帮助活动举办者更好地了解用户需求,制定更精准的营销策略。

综上所述,活动报名与缴费小程序的开发背景主要是为了适应数字化时代的需求,提高效率、提供更好的用户体验,增加安全性,以及为数据分析和市场营销提供支持。这种数字化解决方案在现代社会得到了广泛应用,并且不断发展壮大。

功能规划

  • 1 用户注册与登录:用户可以注册一个账号并使用该账号登录,以便管理他们的报名和支付信息。
  • 2 活动浏览与搜索:用户可以浏览发布的活动列表,并使用搜索功能查找感兴趣的活动。
  • 3 活动详情:用户可以点击活动列表中的活动,查看活动的详细信息,包括活动介绍、时间、地点、费用等。
  • 4 报名流程:用户可以选择要参加的活动,并填写报名表格。表格可能包括个人信息(姓名、联系方式,后台可以自定义)、参与人数、特殊要求等。
  • 5 支付功能:用户可以选择微信支付完成报名费用的支付。支付完成后,系统会生成报名确认凭证。
  • 6 用户报名管理:用户可以查看已经报名的活动列表,以及报名状态和详细信息。用户可能还可以编辑报名信息或取消已报名的活动。
  • 7 评价与反馈:用户可以对参加的活动进行评价和反馈。他们可以分享他们的活动体验,并提供改进建议。

功能示意图

在这里插入图片描述

数据库设计


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_START_DAY: 'string|false|comment=开始时间',ACTIVITY_END_DAY: 'string|false|comment=截止时间',ACTIVITY_STOP: 'int|true|default=0|comment=报名截止时间 0=永不过期',ACTIVITY_START_MONTH: 'string|false|comment=开始月份',ACTIVITY_END_MONTH: 'string|false|comment=截止月份',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_COMMENT_CNT: 'int|true|default=0',ACTIVITY_METHOD: 'int|true|default=0|comment=支付方式 0=线下,1=线上',ACTIVITY_FEE: 'int|false|comment=支付金额 分',ACTIVITY_JOIN_CNT: 'int|true|default=0',ACTIVITY_PAY_CNT: 'int|true|default=0|comment=支付数',ACTIVITY_PAY_FEE: 'int|true|default=0|comment=支付额',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_CANCEL_TIME: 'int|true|default=0|comment=取消时间',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=报名成功, 98=自己取消,99=审核未过/取消',ACTIVITY_JOIN_REASON: 'string|false|comment=审核拒绝或者取消理由',ACTIVITY_JOIN_FEE: 'int|true|default=0|comment=需支付费用 分',ACTIVITY_JOIN_PAY_TRADE_NO: 'string|false|comment=商家订单号 32位',ACTIVITY_JOIN_PAY_STATUS: 'int|true|default=0|comment=支付状态 0=未支付 1=已支付 8=已退款 99=无需支付',ACTIVITY_JOIN_PAY_FEE: 'int|true|default=0|comment=已支付费用 分',ACTIVITY_JOIN_PAY_TIME: 'int|true|default=0|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 {async minuteJob() {console.log('### minuteJob >>>>>');// 未支付的成功订单取消  let time = this._timestamp - 6 * 60 * 1000;console.log('###### Begin>>> 未支付订单6分钟后取消, time<=' + time + ', ' + timeUtil.timestamp2Time(time));let where = {ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]],ACTIVITY_JOIN_PAY_STATUS: 0,ACTIVITY_JOIN_ADD_TIME: ['<=', time],}let rows = await ActivityJoinModel.getAll(where, '*', {}, 3000, false);console.log('未支付订单6分钟后取消, count=', rows.length);for (let k in rows) {let activityJoin = rows[k];let tradeNo = activityJoin.ACTIVITY_JOIN_PAY_TRADE_NO;if (!await this.fixActivityJoinPay(tradeNo, activityJoin.ACTIVITY_JOIN_ACTIVITY_ID)) {console.log('该报名记录未支付,已取消并删除!', activityJoin);}}console.log('###### END. 未支付订单6分钟后取消');}// 获取当前活动状态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 '报名已满';elsereturn '报名中';}/** 浏览信息 */async viewActivity(userId, id) {await this.fixUserActivityJoinPayRecord(userId);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) ? '待审核' : '已报名';if (activity.myActivityJoinTag == '已报名' && activityJoin.ACTIVITY_JOIN_PAY_STATUS == 1) {activity.myActivityJoinTag = '已报名缴费';}}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_START_MONTH,ACTIVITY_END_MONTH,ACTIVITY_START_DAY,ACTIVITY_END_DAY,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 time = timeUtil.time('Y-M-D');where.and.ACTIVITY_START_DAY = ['<=', time];where.and.ACTIVITY_END_DAY = ['>=', time];break;}case 'tomorrow': { //明日let time = timeUtil.time('Y-M-D', 86400);where.and.ACTIVITY_START_DAY = ['<=', time];where.and.ACTIVITY_END_DAY = ['>=', time];break;}case 'month': { //本月let month = timeUtil.time('Y-M');where.and.ACTIVITY_START_MONTH = ['<=', month];where.and.ACTIVITY_END_MONTH = ['>=', month];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}) {await this.fixUserActivityJoinPayRecord(userId);orderBy = orderBy || {'ACTIVITY_JOIN_ADD_TIME': 'desc'};let fields = 'ACTIVITY_JOIN_PAY_STATUS,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 'usercancel': {where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.CANCEL;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 fixUserActivityJoinPayRecord(userId) {let where = {ACTIVITY_JOIN_USER_ID: userId,ACTIVITY_JOIN_PAY_STATUS: 0,ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]],}let list = await ActivityJoinModel.getAll(where);for (let k = 0; k < list.length; k++) {await this.fixActivityJoinPay(list[k].ACTIVITY_JOIN_PAY_TRADE_NO, list[k].ACTIVITY_JOIN_ACTIVITY_ID);}}// 修正某订单状态 (仅需支付订单)async fixActivityJoinPay(tradeNo, activityId) {if (!tradeNo) {// 无支付号空单 删除await ActivityJoinModel.del({ ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo });// 重新统计this.statActivityJoin(activityId);return false;}let payService = new PayService();if (!await payService.fixPayResult(tradeNo)) {// 关闭未支付单payService.closePay(tradeNo);// 未支付 await ActivityJoinModel.del({ ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo });// 重新统计this.statActivityJoin(activityId);return false;}// 已支付let pay = await PayModel.getOne({ PAY_TRADE_NO: tradeNo });if (!pay) this.AppError('支付流水异常,请核查');// 更新支付信息let data = {ACTIVITY_JOIN_PAY_STATUS: 1,ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo,ACTIVITY_JOIN_PAY_FEE: pay.PAY_TOTAL_FEE,ACTIVITY_JOIN_PAY_TIME: pay.PAY_END_TIME,}await ActivityJoinModel.edit({ ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo }, data);// 重新统计this.statActivityJoin(activityId);return true;}//################## 报名  async statActivityJoin(activityId) {// 报名数let where = {ACTIVITY_JOIN_ACTIVITY_ID: activityId,ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]}let cnt = await ActivityJoinModel.count(where);// 已支付记录let wherePayCnt = {ACTIVITY_JOIN_ACTIVITY_ID: activityId,ACTIVITY_JOIN_PAY_STATUS: 1,ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]}let payCnt = await ActivityJoinModel.count(wherePayCnt);// 已支付金额let wherePayFee = {ACTIVITY_JOIN_ACTIVITY_ID: activityId,ACTIVITY_JOIN_PAY_STATUS: 1,ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]}let payFee = await ActivityJoinModel.sum(wherePayFee, 'ACTIVITY_JOIN_PAY_FEE');// 报名用户头像列表let whereUserList = {ACTIVITY_JOIN_ACTIVITY_ID: activityId,ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC,ACTIVITY_JOIN_PAY_STATUS: ['in', [1, 99]]}let joinParams = {from: UserModel.CL,localField: 'ACTIVITY_JOIN_USER_ID',foreignField: 'USER_MINI_OPENID',as: 'user',};let orderBy = {ACTIVITY_JOIN_ADD_TIME: 'desc'}let userList = await ActivityJoinModel.getListJoin(joinParams, whereUserList, 'ACTIVITY_JOIN_ADD_TIME,user.USER_MINI_OPENID,user.USER_NAME,user.USER_PIC', orderBy, 1, 6, false, 0);userList = userList.list;for (let k = 0; k < userList.length; k++) {userList[k] = userList[k].user;}let data = {ACTIVITY_JOIN_CNT: cnt,ACTIVITY_PAY_CNT: payCnt,ACTIVITY_PAY_FEE: payFee,ACTIVITY_USER_LIST: userList}await ActivityModel.edit(activityId, data);}/**  报名前获取关键信息 */async detailForActivityJoin(userId, activityId) {await this.fixUserActivityJoinPayRecord(userId);let fields = 'ACTIVITY_JOIN_FORMS, ACTIVITY_TITLE, ACTIVITY_FEE, ACTIVITY_METHOD';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 joinList = await ActivityJoinModel.getAll(whereMy, 'ACTIVITY_JOIN_OBJ,ACTIVITY_JOIN_FORMS', orderByMy);let addressList = [];let addressList2 = [];for (let k = 0; k < joinList.length; k++) {let exist = false;for (let j = 0; j < addressList.length; j++) {if (addressList[j].name === joinList[k].ACTIVITY_JOIN_OBJ.name) {exist = true;break;}}if (!exist) {addressList.push(joinList[k].ACTIVITY_JOIN_OBJ);addressList2.push(joinList[k].ACTIVITY_JOIN_FORMS);}}// 取出本人最近一次的填写表单let joinMy = await ActivityJoinModel.getOne(whereMy, 'ACTIVITY_JOIN_FORMS', orderByMy);joinMy = null;let myForms = joinMy ? joinMy.ACTIVITY_JOIN_FORMS : [];activity.myForms = myForms;activity.addressList = addressList;activity.addressList2 = addressList2;activity.ACTIVITY_FEE = Number(dataUtil.fmtMoney(activity.ACTIVITY_FEE / 100));return activity;}/** 取消我的报名 只有成功和待审核可以取消   */async cancelMyActivityJoin(userId, activityJoinId) {let where = {_id: activityJoinId,ACTIVITY_JOIN_USER_ID: userId,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('该活动已经截止报名,不能取消');if (activityJoin.ACTIVITY_JOIN_PAY_STATUS == 99) {// 无须支付// 更新记录 let data = {ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.CANCEL,ACTIVITY_JOIN_CANCEL_TIME: this._timestamp,}await ActivityJoinModel.edit(activityJoinId, data);}else {let tradeNo = activityJoin.ACTIVITY_JOIN_PAY_TRADE_NO;if (!await this.fixActivityJoinPay(tradeNo, activityJoin.ACTIVITY_JOIN_ACTIVITY_ID)) {this.AppError('该报名记录未支付,已取消并删除!');}let payService = new PayService();await payService.refundPay(tradeNo, '用户取消报名');// 更新记录 let data = {ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.CANCEL,ACTIVITY_JOIN_CANCEL_TIME: this._timestamp,ACTIVITY_JOIN_PAY_STATUS: 8,}await ActivityJoinModel.edit(activityJoinId, data);}// 统计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设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

后台设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

git源码

源码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/149884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【小沐学前端】Node.js实现基于Protobuf协议的UDP通信(UDP/TCP)

文章目录 1、简介1.1 node1.2 Protobuf 2、下载和安装2.1 node2.2 Protobuf2.2.1 安装2.2.2 工具 3、node 代码示例3.1 HTTP3.2 UDP单播3.4 UDP广播 4、Protobuf 代码示例4.1 例子: awesome.proto4.1.1 加载.proto文件方式4.1.2 加载.json文件方式4.1.3 加载.js文件方式 4.2 例…

gorm 自定义时间、字符串数组类型

文章目录 自定义时间类型自定义字符串数组测试与完整代码测试代码测试结果 GORM 是GO语言中一款强大友好的ORM框架&#xff0c;但在使用过程中内置的数据类型不能满足以下两个需求&#xff0c;如下&#xff1a; time.Time类型返回的是 2023-10-03T09:12:08.5352808:00这种字符串…

FreeRTOS入门教程(队列详细使用示例)

文章目录 前言一、队列基本使用二、如何分辨数据源三、传输大块数据总结 前言 上篇文章我们已经讲解了队列的概念和队列相关的API函数&#xff0c;那么本篇文章的话就开始带大家来学习使用队列。 一、队列基本使用 这个例子将会创建三个任务&#xff0c;其中两个任务用来发送…

完美清晰,炫酷畅享——Perfectly Clear Video为你带来卓越的AI视频增强体验

在我们日常生活中&#xff0c;我们经常会拍摄和观看各种视频内容&#xff0c;无论是旅行记录、家庭聚会还是商务演示&#xff0c;我们都希望能够呈现出最清晰、最精彩的画面效果。而现在&#xff0c;有一个强大的工具可以帮助我们实现这一目标&#xff0c;那就是Perfectly Clea…

谁“动”了我的信息?

通信公司“内鬼” 批量提供手机卡 超6万张手机卡用来发涉赌短信 2023年10月2日&#xff0c;据报道2022年12月&#xff0c;湖北省公安厅“雷火”打击整治治安突出问题专项行动指挥部研判发现&#xff0c;有人在湖北随州利用虚拟拨号设备GOIP发出大量赌博短信。随州市公安局研判…

【数据结构--八大排序】之快速排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Flink--9、双流联结(窗口联结、间隔联结)

星光下的赶路人star的个人主页 我还有改变的可能性&#xff0c;一想起这点&#xff0c;我就心潮澎湃 文章目录 1、基于时间的合流——双流联结&#xff08;Join&#xff09;1.1 窗口联结&#xff08;Window Join&#xff09;1.2 间隔联结&#xff08;Interval Join&#xff09;…

苹果手机怎么备份所有数据?2023年iPhone 15数据备份常用的3种方法!

当苹果手机需要进行刷机、恢复出厂设置、降级iOS系统等操作时&#xff0c;我们需要将自己的iPhone数据提前进行备份。 特别是在苹果发布新iOS系统时&#xff0c;总有一些小伙伴因为升降级系统&#xff0c;而导致了重要数据的丢失。 iPhone中储存着重要的照片、通讯录、文件等数…

出去重复的列值(关键词:distinct)

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: select distinct 列名 from 表名; 案例&#xff1a;查询emp表中&#xff0c;员工的职位&#xff08;job&#xff09;&#xff0c;并去重…

Redis-分布式锁

分布式锁相关内容 超卖问题切入可以使用互斥锁给先获取到锁的线程加锁吗&#xff1f;使用redis分布式锁解决超卖问题setnx命令实现分布式锁为什么需要设置过期时间&#xff1f;Redis实现分布式锁如何合理控制锁的有效时长 redisson实现分布式锁 超卖问题切入 我们先来看一个项目…

【Docker内容大集合】Docker从认识到实践再到底层原理大汇总

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/categ…

前端TypeScript学习day01-TS介绍与TS部分常用类型

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 TypeScript 介绍 TypeScript 是什么 TypeScript 为什么要为 JS 添加类型支持&#xff1f; TypeScript 相…

【已解决】spring-boot项目使用maven打包时出现BOOT-INF文件夹的问题

jar中多了这个BOOT-INF文件夹的原因&#xff0c;主要是因为我们在maven的pom文件中加入了spring-boot-maven-plugin这个插件&#xff0c;如下所示&#xff1a; 只需要将加个configuration标签&#xff0c;并在里面嵌套加入一个skip子标签&#xff0c;并将skip的值设为true&…

vulnhub靶机doubletrouble

下载地址&#xff1a;doubletrouble: 1 ~ VulnHub 主机发现 arp-scan -l 端口扫描 nmap --min-rate 1000 -p- 192.168.21.151 端口服务扫描 nmap -sV -sT -O -p22,80 192.168.21.151 漏洞扫描 nmap --scriptvuln -p22,80 192.168.21.151 先去看看web页面 这里使用的是qdpm …

如何一步步优化负载均衡策略

发展到一定阶段后&#xff0c;Web 应用程序就会增长到单服务器部署无法承受的地步。这时候企业要么提升可用性&#xff0c;要么提升可扩展性&#xff0c;甚至两者兼而有之。为此&#xff0c;他们会将应用程序部署在多台服务器上&#xff0c;并在服务器之前使用负载均衡器来分配…

pycharm配置python3.8版本专门用于undecteded_chromedriver测试

pycharm配置python3.8版本专门用于undecteded_chromedriver测试 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://pay.xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、Pycharm及python环境的配置 1.安装python-3.8.7rc1-amd64.e…

医学影像归档与通讯系统(PACS)系统源码 PACS三维图像后处理技术

医学影像归档与通讯系统&#xff08;PACS&#xff09;系统源码 PACS三维图像处理 医学影像归档与通讯系统&#xff08;PACS&#xff09;系统&#xff0c;是一套适用于从单一影像设备到放射科室、到全院级别等各种应用规模的医学影像归档与通讯系统。PACS集患者登记、图像采集、…

NUWA论文阅读

论文链接&#xff1a;NUWA: Visual Synthesis Pre-training for Neural visUal World creAtion 文章目录 摘要引言相关工作视觉自回归模型视觉稀疏自注意 方法3D数据表征3D Nearby Self-Attention3D编码器-解码器训练目标 实验实现细节与SOTA比较T2I微调T2V微调V2V微调Sketch-t…

基于SpringBoot的信息化在线教学平台的设计与实现

目录 前言 一、技术栈 二、系统功能介绍 学生信息管理 教师信息管理 学生成绩管理 留言板 学生注册管理 留言反馈 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已…

数据结构 2.1 单链表

1.单链表 线性表&#xff1a;1.有限的序列 2.序列中的每一个元素都有唯一的前驱和后继&#xff0c;除了开头和结尾的两个节点。 顺序表&#xff1a;分配一块连续的内存去存放这些元素&#xff0c;eg、数组 链表&#xff1a;内存是不连续的&#xff0c;元素会各自被分配一块内…