从0到1:理发店预约剪发小程序开发笔记(上)

背景

理发师可以在小程序上设置自己的可预约时间,价格,自我介绍,顾客可以根据理发师的日程安排选择合适的时间进行预约和支付。这样可以提高预约的效率,减少沟通成本,方便双方的安排。

功能规划

  • 首页展示:展示最新的公告通知、理发师推荐等内容,吸引用户关注。
  • 理发师列表:列出所有在班的理发师信息,包括个人简介、星级、理发价格等,方便用户选择合适的理发师。
  • 预约功能:用户可以根据自己的需求选择理发师,进行预约并支付费用。
  • 预约管理:用户可以查看自己的预约记录,包括已完成的预约和待核销的预约,也可以取消预约(所支付费用原路退回)。

功能图

在这里插入图片描述

数据库设计

MeetModel.DB_STRUCTURE = {_pid: 'string|true',MEET_ID: 'string|true',MEET_ADMIN_ID: 'string|true|comment=添加的管理员',MEET_TITLE: 'string|true|comment=标题',MEET_JOIN_FORMS: 'array|true|default=[]|comment=表单字段设置',MEET_DAYS: 'array|true|default=[]|comment=最近一次修改保存的可用日期',MEET_CATE_ID: 'string|true|comment=分类编号',MEET_CATE_NAME: 'string|true|comment=分类冗余',MEET_FORMS: 'array|true|default=[]',MEET_OBJ: 'object|true|default={}',MEET_CANCEL_SET: 'int|true|default=1|comment=取消设置 0=不允,1=允许,2=仅开始前可取消',MEET_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中,9=停止预约,10=已关闭',MEET_ORDER: 'int|true|default=9999',MEET_VOUCH: 'int|true|default=0',MEET_QR: 'string|false',MEET_FEE: 'int|false|comment=支付金额 分',MEET_PAY_CNT: 'int|true|default=0|comment=支付数',MEET_PAY_FEE: 'int|true|default=0|comment=支付额',MEET_JOIN_CNT: 'int|true|default=0|comment=预约数',MEET_PHONE: 'string|false|comment=登录手机',MEET_PASSWORD: 'string|false|comment=登录密码',MEET_TOKEN: 'string|false|comment=当前登录token',MEET_TOKEN_TIME: 'int|true|default=0|comment=当前登录token time',MEET_MINI_OPENID: 'string|false|comment=小程序openid',MEET_LOGIN_CNT: 'int|true|default=0|comment=登陆次数',MEET_LOGIN_TIME: 'int|false|comment=最近登录时间',MEET_VIEW_CNT: 'int|true|default=0',MEET_ADD_TIME: 'int|true',MEET_EDIT_TIME: 'int|true',MEET_ADD_IP: 'string|false',MEET_EDIT_IP: 'string|false',
};ProductModel.DB_STRUCTURE = {_pid: 'string|true',PRODUCT_ID: 'string|true',PRODUCT_TITLE: 'string|true|comment=标题', PRODUCT_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中',PRODUCT_CATE_ID: 'string|true|default=0|comment=分类',PRODUCT_CATE_NAME: 'string|false|comment=分类冗余',PRODUCT_ORDER: 'int|true|default=9999',PRODUCT_VOUCH: 'int|true|default=0',PRODUCT_FORMS: 'array|true|default=[]',PRODUCT_OBJ: 'object|true|default={}',PRODUCT_QR: 'string|false',PRODUCT_VIEW_CNT: 'int|true|default=0',PRODUCT_ADD_TIME: 'int|true',PRODUCT_EDIT_TIME: 'int|true',PRODUCT_ADD_IP: 'string|false',PRODUCT_EDIT_IP: 'string|false',
};

核心实现

class MeetService extends BaseProjectService {constructor() {super();this._log = new LogUtil(projectConfig.MEET_LOG_LEVEL);}/*** 抛出异常* @param {*} msg * @param {*} code */AppError(msg) {this._log.error(msg);super.AppError(msg);}_meetLog(meet, func = '', msg = '') {let str = '';str = `[MEET=${meet.MEET_TITLE}][${func}] ${msg}`;this._log.debug(str);}async minuteJob() {console.log('### minuteJob >>>>>');// 未支付的成功订单取消  let time = this._timestamp - 6 * 60 * 1000;console.log('###### Begin>>> 未支付订单6分钟后取消, time<=' + time + ', ' + timeUtil.timestamp2Time(time));let where = {JOIN_STATUS: ['in', [JoinModel.STATUS.SUCC]],JOIN_PAY_STATUS: 0,JOIN_ADD_TIME: ['<=', time],}let rows = await JoinModel.getAll(where, '*', {}, 3000, false);console.log('未支付订单6分钟后取消, count=', rows.length);for (let k in rows) {let join = rows[k];let tradeNo = join.JOIN_PAY_TRADE_NO;if (!await this.fixMeetJoinPay(tradeNo, join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK)) {console.log('该报名记录未支付,已取消并删除!', join);}}console.log('###### END. 未支付订单6分钟后取消');}/** 统一获取Meet(某天) */async getMeetOneDay(meetId, day, where, fields = '*') {let meet = await MeetModel.getOne(where, fields);if (!meet) return meet;meet.MEET_DAYS_SET = await this.getDaysSet(meetId, day, day);return meet;}/** 获取日期设置 */async getDaysSet(meetId, startDay, endDay = null) {let where = {DAY_MEET_ID: meetId}if (startDay && endDay && endDay == startDay)where.day = startDay;else if (startDay && endDay)where.day = ['between', startDay, endDay];else if (!startDay && endDay)where.day = ['<=', endDay];else if (startDay && !endDay)where.day = ['>=', startDay];let orderBy = {'day': 'asc'}let list = await DayModel.getAllBig(where, 'day,dayDesc,times', orderBy, 1000);for (let k = 0; k < list.length; k++) {delete list[k]._id;}return list;}// 按时段统计某时段报名情况async statJoinCnt(meetId, timeMark) {let whereDay = {DAY_MEET_ID: meetId,day: this.getDayByTimeMark(timeMark)};let day = await DayModel.getOne(whereDay, 'times');if (!day) return;let whereJoin = {JOIN_MEET_TIME_MARK: timeMark,JOIN_MEET_ID: meetId};let ret = await JoinModel.groupCount(whereJoin, 'JOIN_STATUS');let stat = { //统计数据succCnt: ret['JOIN_STATUS_1'] || 0, //1=预约成功,cancelCnt: ret['JOIN_STATUS_10'] || 0, //10=已取消, adminCancelCnt: ret['JOIN_STATUS_99'] || 0, //99=后台取消};let times = day.times;for (let j in times) {if (times[j].mark === timeMark) {let data = {['times.' + j + '.stat']: stat}await DayModel.edit(whereDay, data);return;}}}// 预约前检测async beforeJoin(userId, meetId, timeMark) {await this.checkMeetRules(userId, meetId, timeMark);}// 修正某用户所有未支付的成功订单状态,无须支付的不用处理async fixUserMeetJoinPayRecord(userId) {let where = {JOIN_USER_ID: userId,JOIN_PAY_STATUS: 0,JOIN_STATUS: ['in', [JoinModel.STATUS.SUCC]],}let list = await JoinModel.getAll(where);for (let k = 0; k < list.length; k++) {await this.fixMeetJoinPay(list[k].JOIN_PAY_TRADE_NO, list[k].JOIN_MEET_ID, list[k].JOIN_MEET_TIME_MARK);}}// 修正某订单状态 (仅需支付订单)async fixMeetJoinPay(tradeNo, meetId, timeMark) {if (!tradeNo) {// 无支付号空单 删除await JoinModel.del({ JOIN_PAY_TRADE_NO: tradeNo });// 重新统计this.statJoinCnt(meetId, timeMark);return false;}let payService = new PayService();if (!await payService.fixPayResult(tradeNo)) {// 关闭未支付单payService.closePay(tradeNo);// 未支付 await JoinModel.del({ JOIN_PAY_TRADE_NO: tradeNo });// 重新统计this.statJoinCnt(meetId, timeMark);return false;}// 已支付let pay = await PayModel.getOne({ PAY_TRADE_NO: tradeNo });if (!pay) this.AppError('支付流水异常,请核查');// 更新支付信息let data = {JOIN_PAY_STATUS: 1,JOIN_PAY_TRADE_NO: tradeNo,JOIN_PAY_FEE: pay.PAY_TOTAL_FEE,JOIN_PAY_TIME: pay.PAY_END_TIME,}await JoinModel.edit({ JOIN_PAY_TRADE_NO: tradeNo }, data);// 重新统计this.statJoinCnt(meetId, timeMark);return true;}// 用户预约逻辑async prepay(userId, meetId, timeMark, formsList) {}// 根据日期获取其所在天设置getDaySetByDay(meet, day) {for (let k = 0; k < meet.MEET_DAYS_SET.length; k++) {if (meet.MEET_DAYS_SET[k].day == day)return dataUtil.deepClone(meet.MEET_DAYS_SET[k]);}return null;}// 根据时段标识获取其所在天 getDayByTimeMark(timeMark) {return timeMark.substr(1, 4) + '-' + timeMark.substr(5, 2) + '-' + timeMark.substr(7, 2);}// 根据时段标识获取其所在天设置getDaySetByTimeMark(meet, timeMark) {let day = this.getDayByTimeMark(timeMark);for (let k = 0; k < meet.MEET_DAYS_SET.length; k++) {if (meet.MEET_DAYS_SET[k].day == day)return dataUtil.deepClone(meet.MEET_DAYS_SET[k]);}return null;}// 根据时段标识获取其所在时段设置getTimeSetByTimeMark(meet, timeMark) {let day = this.getDayByTimeMark(timeMark);for (let k = 0; k < meet.MEET_DAYS_SET.length; k++) {if (meet.MEET_DAYS_SET[k].day != day) continue;for (let j in meet.MEET_DAYS_SET[k].times) {if (meet.MEET_DAYS_SET[k].times[j].mark == timeMark)return dataUtil.deepClone(meet.MEET_DAYS_SET[k].times[j]);}}return null;}// 预约时段人数和状态控制校验async checkMeetTimeControll(meet, timeMark, meetPeopleCnt = 1) {if (!meet) this.AppError('预约时段设置错误, 预约项目不存在');let daySet = this.getDaySetByTimeMark(meet, timeMark); // 当天设置let timeSet = this.getTimeSetByTimeMark(meet, timeMark); // 预约时段设置if (!daySet || !timeSet) this.AppError('预约时段设置错误day&time');let statusDesc = timeSet.status == 1 ? '开启' : '关闭';let limitDesc = '';if (timeSet.isLimit) {limitDesc = '人数上限MAX=' + timeSet.limit;} elselimitDesc = '人数不限制NO';this._meetLog(meet, `------------------------------`);this._meetLog(meet, `#预约时段控制,预约日期=<${daySet.day}>`, `预约时段=[${timeSet.start}-${timeSet.end}],状态=${statusDesc}, ${limitDesc} 当前预约成功人数=${timeSet.stat.succCnt}`);if (timeSet.status == 0) this.AppError('该时段预约已经关闭,请选择其他');// 时段总人数限制if (timeSet.isLimit) {if (timeSet.stat.succCnt >= timeSet.limit) {this.AppError('该时段预约人员已满,请选择其他');}let maxCnt = timeSet.limit - timeSet.stat.succCnt;if (maxCnt < meetPeopleCnt) {this.AppError('本时段最多还可以预约' + (maxCnt) + '人,您当前提交了' + meetPeopleCnt + '人,请调整后再提交');}}}/** 报名规则校验 */async checkMeetRules(userId, meetId, timeMark, formsList = null) {// 预约时段是否存在let meetWhere = {_id: meetId};let day = this.getDayByTimeMark(timeMark);let meet = await this.getMeetOneDay(meetId, day, meetWhere);if (!meet) {this.AppError('预约时段选择错误,请重新选择');}// 预约时段人数和状态控制校验let meetPeopleCnt = formsList ? formsList.length : 1;await this.checkMeetTimeControll(meet, timeMark, meetPeopleCnt);// 截止规则  await this.checkMeetEndSet(meet, timeMark);// 针对用户的次数限制await this.checkMeetLimitSet(userId, meet, timeMark, meetPeopleCnt);}// 预约次数限制校验async checkMeetLimitSet(userId, meet, timeMark, nowCnt) {if (!meet) this.AppError('预约次数规则错误, 预约项目不存在');let meetId = meet._id;let daySet = this.getDaySetByTimeMark(meet, timeMark); // 当天设置let timeSet = this.getTimeSetByTimeMark(meet, timeMark); // 预约时段设置this._meetLog(meet, `------------------------------`);this._meetLog(meet, `#预约次数规则,预约日期=<${daySet.day}>`, `预约时段=[${timeSet.start}~${timeSet.end}]`);let where = {JOIN_MEET_ID: meetId,JOIN_MEET_TIME_MARK: timeMark,JOIN_USER_ID: userId,JOIN_STATUS: JoinModel.STATUS.SUCC}let cnt = await JoinModel.count(where);let maxCnt = projectConfig.MEET_MAX_JOIN_CNT;this._meetLog(meet, `预约次数规则,mode=本时段可预约${maxCnt}次`, `当前已预约=${cnt}次`);if (cnt >= maxCnt)this.AppError(`您本时段已经预约,不能继续预约`);}// 预约截止设置校验async checkMeetEndSet(meet, timeMark) {if (!meet) this.AppError('预约截止规则错误, 预约项目不存在');this._meetLog(meet, `------------------------------`);let daySet = this.getDaySetByTimeMark(meet, timeMark); // 当天设置let timeSet = this.getTimeSetByTimeMark(meet, timeMark); // 预约时段设置this._meetLog(meet, `#预约截止规则,预约日期=<${daySet.day}>`, `预约时段=[${timeSet.start}-${timeSet.end}]`);let nowTime = timeUtil.time('Y-M-D h:m:s');/*let startTime = daySet.day + ' ' + timeSet.start + ':00';this._meetLog(meet, `预约开始规则,mode=<时段过期判定>`, `预约开始时段=${startTime},当前时段=${nowTime}`);if (nowTime > startTime) {this.AppError('该时段已开始,无法预约,请选择其他');}*/let endTime = daySet.day + ' ' + timeSet.end + ':59';this._meetLog(meet, `预约开始规则,mode=<时段过期判定>`, `预约结束时段=${endTime},当前时段=${nowTime}`);if (nowTime > endTime) {this.AppError('该时段已结束,无法预约,请选择其他');}}/**  预约详情 */async viewMeet(userId, meetId) {await this.fixUserMeetJoinPayRecord(userId);let fields = '*';let where = {_id: meetId,MEET_STATUS: ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]}let meet = await MeetModel.getOne(where, fields);if (!meet) return null;MeetModel.inc(meetId, 'MEET_VIEW_CNT', 1);let getDaysSet = [];meet.MEET_DAYS_SET = await this.getDaysSet(meetId, timeUtil.time('Y-M-D')); //今天及以后let daysSet = meet.MEET_DAYS_SET;let now = timeUtil.time('Y-M-D');for (let k = 0; k < daysSet.length; k++) {let dayNode = daysSet[k];if (dayNode.day < now) continue; // 排除过期let getTimes = [];for (let j in dayNode.times) {let timeNode = dayNode.times[j];// 排除状态关闭的时段if (timeNode.status != 1) continue;// 判断数量是否已满if (timeNode.isLimit && timeNode.stat.succCnt >= timeNode.limit)timeNode.error = '预约已满';// 截止规则if (!timeNode.error) {try {await this.checkMeetEndSet(meet, timeNode.mark);} catch (ex) {if (ex.name == 'AppError')timeNode.error = '预约结束';elsethrow ex;}}getTimes.push(timeNode);}dayNode.times = getTimes;getDaysSet.push(dayNode);}// 预约人数let whereJoin = {JOIN_MEET_ID: meetId,JOIN_STATUS: JoinModel.STATUS.SUCC}let cntJoin = await JoinModel.count(whereJoin);// 收藏人数let FavModel = require('../model/fav_model.js');let whereFav = {FAV_OID: meetId,}let cntFav = await FavModel.count(whereFav);// 只返回需要的字段let ret = {};ret.cntJoin = cntJoin;ret.cntFav = cntFav;ret.MEET_DAYS_SET = getDaysSet;ret.MEET_FEE = meet.MEET_FEE;ret.MEET_QR = meet.MEET_QR;ret.MEET_TITLE = meet.MEET_TITLE;ret.MEET_CATE_NAME = meet.MEET_CATE_NAME;ret.MEET_CATE_ID = meet.MEET_CATE_ID;ret.MEET_OBJ = meet.MEET_OBJ;ret.MEET_VIEW_CNT = meet.MEET_VIEW_CNT;ret.cover = await cloudUtil.getTempFileURLOne(meet.MEET_OBJ.cover[0]);return ret;}/**  预约前获取关键信息 */async detailForJoin(userId, meetId, timeMark) {await this.fixUserMeetJoinPayRecord(userId);let fields = 'MEET_FEE,MEET_DAYS_SET,MEET_JOIN_FORMS, MEET_TITLE';let where = {_id: meetId,MEET_STATUS: ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]}let day = this.getDayByTimeMark(timeMark);let meet = await this.getMeetOneDay(meetId, day, where, fields);if (!meet) return null;let dayDesc = timeUtil.fmtDateCHN(this.getDaySetByTimeMark(meet, timeMark).day);let timeSet = this.getTimeSetByTimeMark(meet, timeMark);let timeDesc = timeSet.start + '~' + timeSet.end;meet.dayDesc = dayDesc + ' ' + timeDesc;// 取出本人最近一次本时段填写表单let whereMy = {JOIN_USER_ID: userId,}let orderByMy = {JOIN_ADD_TIME: 'desc'}let joinMy = await JoinModel.getOne(whereMy, 'JOIN_FORMS', orderByMy);if (joinMy)meet.myForms = joinMy.JOIN_FORMS;elsemeet.myForms = [];meet.MEET_FEE = Number(dataUtil.fmtMoney(meet.MEET_FEE / 100));return meet;}/** 获取某天可用时段 */async getUsefulTimesByDaysSet(meetId, day) {let where = {DAY_MEET_ID: meetId,day}let daysSet = await DayModel.getAll(where, 'day,times');let usefulTimes = [];for (let k = 0; k < daysSet.length; k++) {if (daysSet[k].day != day)continue;let times = daysSet[k].times;for (let j in times) {if (times[j].status != 1) continue;usefulTimes.push(times[j]);}break;}return usefulTimes;}/** 按天获取预约项目 */async getMeetListByDay(day) {let where = {MEET_STATUS: MeetModel.STATUS.COMM,};let orderBy = {'MEET_ORDER': 'asc','MEET_ADD_TIME': 'desc'};let fields = 'MEET_TITLE,MEET_DAYS_SET,MEET_OBJ.cover';let list = await MeetModel.getAll(where, fields, orderBy);let retList = [];for (let k = 0; k < list.length; k++) {let usefulTimes = await this.getUsefulTimesByDaysSet(list[k]._id, day);if (usefulTimes.length == 0) continue;let node = {};node.timeDesc = usefulTimes.length > 1 ? usefulTimes.length + '个时段' : usefulTimes[0].start;node.title = list[k].MEET_TITLE;node.pic = list[k].MEET_OBJ.cover;node._id = list[k]._id;retList.push(node);}return retList;}/** 获取从某天开始可预约的日期 */async getHasDaysFromDay(day) {let where = {day: ['>=', day],};let fields = 'times,day';let list = await DayModel.getAllBig(where, fields);let retList = [];for (let k = 0; k < list.length; k++) {for (let n in list[k].times) {if (list[k].times[n].status == 1) {retList.push(list[k].day);break;}}}return retList;}/** 取得预约分页列表 */async getMeetList({search, // 搜索条件sortType, // 搜索菜单sortVal, // 搜索菜单orderBy, // 排序 cateId, //分类查询条件page,size,isTotal = true,oldTotal}) {orderBy = orderBy || {'MEET_ORDER': 'asc','MEET_ADD_TIME': 'desc'};let fields = 'MEET_TITLE,MEET_OBJ,MEET_DAYS,MEET_CATE_NAME,MEET_CATE_ID,MEET_FEE';let where = {};where.and = {_pid: this.getProjectId() //复杂的查询在此处标注PID};if (cateId && cateId !== '0') where.and.MEET_CATE_ID = cateId;where.and.MEET_STATUS = ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]; // 状态  if (util.isDefined(search) && search) {where.or = [{ MEET_TITLE: ['like', search] },];} else if (sortType && util.isDefined(sortVal)) {// 搜索菜单switch (sortType) {case 'sort': {orderBy = this.fmtOrderBySort(sortVal, 'NEWS_ADD_TIME');break;}case 'cateId': {if (sortVal) where.and.MEET_CATE_ID = String(sortVal);break;}}}let result = await MeetModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);return result;}/** 取消我的预约 只有成功可以取消 */async cancelMyJoin(userId, joinId) {let where = {JOIN_USER_ID: userId,_id: joinId,JOIN_IS_CHECKIN: 0, // 核销不能取消JOIN_STATUS: JoinModel.STATUS.SUCC};let join = await JoinModel.getOne(where);if (!join) {this.AppError('未找到可取消的预约记录');}// 取消规则判定let whereMeet = {_id: join.JOIN_MEET_ID,MEET_STATUS: ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]}let meet = await this.getMeetOneDay(join.JOIN_MEET_ID, join.JOIN_MEET_DAY, whereMeet);if (!meet) this.AppError('预约项目不存在或者已关闭');let daySet = this.getDaySetByTimeMark(meet, join.JOIN_MEET_TIME_MARK);let timeSet = this.getTimeSetByTimeMark(meet, join.JOIN_MEET_TIME_MARK);if (!timeSet) this.AppError('被取消的时段不存在');if (meet.MEET_CANCEL_SET == 0)this.AppError('该预约不能取消');let startT = daySet.day + ' ' + timeSet.start + ':00';let startTime = timeUtil.time2Timestamp(startT);let now = timeUtil.time();if (meet.MEET_CANCEL_SET == 2 && now > startTime)this.AppError('该预约时段已经开始,无法取消');// TODO 已过期不能取消if (join.JOIN_PAY_STATUS == 99) {// 无须支付// 更新记录 let data = {JOIN_STATUS: JoinModel.STATUS.CANCEL,JOIN_CANCEL_TIME: this._timestamp,}await JoinModel.edit(joinId, data);}else {let tradeNo = join.JOIN_PAY_TRADE_NO;if (!await this.fixMeetJoinPay(tradeNo, join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK)) {this.AppError('该预约记录未支付,已取消并删除!');}let payService = new PayService();await payService.refundPay(tradeNo, '用户取消预约');// 更新记录 let data = {JOIN_STATUS: JoinModel.STATUS.CANCEL,JOIN_CANCEL_TIME: this._timestamp,JOIN_PAY_STATUS: 8,}await JoinModel.edit(joinId, data);}// await JoinModel.del(where); 以前是删除,现在改为取消// 统计this.statJoinCnt(join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK);}/** 取得我的预约详情 */async getMyJoinDetail(userId, joinId) {let fields = 'JOIN_PAY_STATUS,JOIN_PAY_FEE,JOIN_COMPLETE_END_TIME,JOIN_IS_CHECKIN,JOIN_CHECKIN_TIME,JOIN_REASON,JOIN_MEET_ID,JOIN_MEET_TITLE,JOIN_MEET_DAY,JOIN_MEET_TIME_START,JOIN_MEET_TIME_END,JOIN_STATUS,JOIN_ADD_TIME,JOIN_CODE,JOIN_FORMS';let where = {_id: joinId,JOIN_USER_ID: userId};return await JoinModel.getOne(where, fields);}/** 取得我的预约分页列表 */async getMyJoinList(userId, {search, // 搜索条件sortType, // 搜索菜单sortVal, // 搜索菜单orderBy, // 排序 page,size,isTotal = true,oldTotal}) {await this.fixUserMeetJoinPayRecord(userId);orderBy = orderBy || {//	'JOIN_MEET_DAY': 'desc',//	'JOIN_MEET_TIME_START': 'desc','JOIN_ADD_TIME': 'desc'};let fields = 'JOIN_COMPLETE_END_TIME,JOIN_IS_CHECKIN,JOIN_REASON,JOIN_MEET_ID,JOIN_MEET_TITLE,JOIN_MEET_DAY,JOIN_MEET_TIME_START,JOIN_MEET_TIME_END,JOIN_STATUS,JOIN_ADD_TIME,JOIN_OBJ';let where = {JOIN_USER_ID: userId};//where.MEET_STATUS = ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]; // 状态  if (util.isDefined(search) && search) {where['JOIN_MEET_TITLE'] = {$regex: '.*' + search,$options: 'i'};} else if (sortType) {// 搜索菜单switch (sortType) {case 'cateId': {if (sortVal) where.JOIN_MEET_CATE_ID = String(sortVal);break;}case 'all': { //所有 break;}case 'use': { //可用未过期where.JOIN_STATUS = JoinModel.STATUS.SUCC;where.JOIN_COMPLETE_END_TIME = ['>=', timeUtil.time('Y-M-D h:m')];break;}case 'check': { //已核销where.JOIN_STATUS = JoinModel.STATUS.SUCC;where.JOIN_IS_CHECKIN = 1;break;}case 'timeout': { //已过期未核销where.JOIN_STATUS = JoinModel.STATUS.SUCC;where.JOIN_IS_CHECKIN = 0;where.JOIN_COMPLETE_END_TIME = ['<', timeUtil.time('Y-M-D h:m')];break;}case 'succ': { //预约成功where.JOIN_STATUS = JoinModel.STATUS.SUCC;//where.JOIN_MEET_DAY = ['>=', timeUtil.time('Y-M-D h:m')];//where.JOIN_MEET_TIME_START = ['>=', timeUtil.time('h:m')];break;}case 'cancel': { //系统已取消where.JOIN_STATUS = ['in', [JoinModel.STATUS.ADMIN_CANCEL]];break;}case 'usercancel': { //用户已取消where.JOIN_STATUS = ['in', [JoinModel.STATUS.CANCEL]];break;}}}let result = await JoinModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);return result;}/** 取得我的某日预约列表 */async getMyJoinSomeday(userId, day) {let fields = 'JOIN_IS_CHECKIN,JOIN_MEET_ID,JOIN_MEET_TITLE,JOIN_MEET_DAY,JOIN_MEET_TIME_START,JOIN_MEET_TIME_END,JOIN_STATUS,JOIN_ADD_TIME';let where = {JOIN_USER_ID: userId,JOIN_MEET_DAY: day};//where.MEET_STATUS = ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]; // 状态  let orderBy = {'JOIN_MEET_TIME_START': 'asc','JOIN_ADD_TIME': 'desc'}return await JoinModel.getAll(where, fields, orderBy);}
}

UI设计

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

理发师平台

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

Git代码分享

git代码下载

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

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

相关文章

基于多种机器学习的豆瓣电影评分预测与多维度可视化【可加系统】

有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 在本研究中&#xff0c;我们采用Python编程语言&#xff0c;利用爬虫技术实时获取豆瓣电影最新数据。通过分析豆瓣网站的结构&#xff0c;我们设计了一套有效的策略来爬取电影相关的JSON格式数据。…

第1章 初识 C 语言

目录 1.1 C 语言的起源 1.2 选择 C 语言的理由 1.2.1 设计特性 1.2.2 高效性 1.2.3 可移植性 1.2.4 强大而灵活 1.2.5 面向程序员 1.2.6 缺点 1.3 C 语言的应用范围 1.4 计算机能做什么 1.5 高级计算机语言和编译器 1.6 语言标准 1.6.1 第 1 个 ANSI/ISO C 标准 …

手写模拟Spring底层原理-简易实现版

通过手写模拟Spring 了解Spring的底层源码启动过程了解BeanDefinition、BeanPostProcessor的概念了解Spring解析配置类等底层源码工作流程了解依赖注入&#xff0c;Aware回调等底层源码工作流程了解Spring AOP的底层源码工作流程 这里实现一个简化版的 Spring 框架的核心功能&a…

GraphRAG深入解析

GraphRAG深入解析 GraphRAG 深入解析概述索引查询 索引过程深入解析步骤 1&#xff1a;处理文本块步骤 2&#xff1a;图提取步骤 3&#xff1a;图增强步骤 4&#xff1a;社区总结步骤 5&#xff1a;文件处理步骤 6&#xff1a;网络可视化 查询过程深入解析本地搜索问题生成全局…

苹果安卓分发的秘密:如何选择正确的渠道(苹果安卓分发)

苹果安卓分发的重要性 随着移动互联网的普及&#xff0c;移动应用程序的开发和分发变得越来越重要。苹果安卓分发是移动应用程序开发者的首要任务之一&#xff0c;因为它直接关系到应用程序的推广和收益。 苹果安卓分发并不是一件简单的事情。开发者需要选择正确的渠道&#…

Pytorch使用教学5-视图view与reshape的区别

有同学后台留言问为什么view有时可对张量进行形变操作&#xff0c;有时就会报错&#xff1f;另外它和reshape功能好像一致&#xff0c;有什么区别呢&#xff1f;本文就带你了解PyTorch中视图的概念。 在PyTorch中对张量进行形变操作时&#xff0c;很多同学也会使用view方法&am…

监测Nginx访问日志状态码,并做相应动作

文章目录 引言I 监测 Nginx 访问日志情况,并做相应动作1.1 前提准备1.2 访问日志 502 情况,重启 bttomcat9服务1.3 其他案例:访问日志 502 情况,重启 php-fpm 服务II 将Shell 脚本check499.sh包装成systemd服务2.1 创建systemd服务2.2 配置service2.3 开机启动2.4 其他常用…

华为ICT大赛之ensp软件BGP原理与配置

BGP基础 1.用于不同自治系统AS(autonomous system)之间动态交换路由信息&#xff1b; BGP取代EGP(exterior gateway protocol)外部网关协议&#xff0c;BGP在其发布路由信息基础上可以进行路由优选&#xff0c;高效处理路由信息&#xff1b; AS:同一组织管理下&#xff0c;使…

RK3568平台(显示篇)显示系统基本概念

一.显示系统概述 linux内核中包含两类图形显示设备驱动框架&#xff1a; FB设备&#xff1a;Framebuffer图形显示框架;DRM&#xff1a;直接渲染管理器&#xff08;Direct Rendering Manager&#xff09;&#xff0c;是linux目前主流的图形显示框架&#xff1b; 在实际场景中…

打通“链上数据脉络” 欧科云链数字生态建设成果凸显

7月25日&#xff0c;据Coindesk报道&#xff0c;全球领先的区块链技术和服务提供商欧科云链宣布旗下OKLink浏览器与Polygon Labs正式达成合作&#xff0c;成为AggLayer首个区块链搜索引擎及Web3数据分析平台&#xff0c;将为开发者提供精简易用的链上数据访问和开发工具&#x…

MATLAB学习教程(一)

目录 1.常见函数基本运算 2.二维绘制: plot(​..) 3.三维绘制: plot3(​..) / mesh(​..) 4.绘图美化 标题及标签 绘图命令&#xff1a;color 绘图命令&#xff1a;Line 5.代码 1.常见函数基本运算 方根函数sqrt() 自然指数函数exp() 以10为底的对数函…

巴斯勒相机(Basler) ACE2 dart 系列说明和软件

巴斯勒相机(Basler) ACE2 dart 系列说明和软件

了解高防 IP

一、高防 IP 的基本概念 高防 IP 是指拥有强大防御能力的 IP 地址。它主要通过将攻击流量引流到高防机房进行清洗和过滤&#xff0c;再将正常的流量回注到源站&#xff0c;从而保障源站服务器的稳定运行。 二、高防 IP 的工作原理 当用户的服务器遭受 DDoS 攻击时&#xff0…

Ubuntu22.04手动安装fabric release-2.5版本

这个过程稍微有点复杂&#xff0c;但完整操作完成以后会对Fabric网络有更加深入的理解&#xff0c;方便后续自己手动搭建Fabric网络。这个过程需要手动逐个下载Fabric源代码、使用命令下载Fabric镜像和用Git下载例子程序。 Fabric源代码主要用途是用来编译cryptogen、configtx…

redis的使用场景

目录 1. 热点数据缓存 1.1 什么是缓存&#xff1f; 1.2 缓存的原理 1.3 什么样的数据适合放入缓存中 1.4 哪个组件可以作为缓存 1.5 java使用redis如何实现缓存功能 1.5.1 需要的依赖 1.5.2 配置文件 1.5.3 代码 1.5.4 发现 1.6 使用缓存注解完成缓存功能 2. 分布式锁…

【Python Web】Flask扩展开发指南

Flask是一个轻量级的Python Web框架&#xff0c;它提供了丰富的扩展库和工具&#xff0c;可以帮助开发者快速构建Web应用。本篇博客将介绍如何进行Flask扩展开发&#xff0c;包括扩展的创建、配置、使用等方面的内容。 目录 Flask扩展开发指南 一、Flask扩展简介 二、创建Fl…

原子操作类(持续更新,未完结)

目录 基本类型原子类数组类型原子类引用类型原子类对象的属性修改原子类原子操作增强类LongAdder 高性能原理说明LongAdder源码深度解析LongAdder小总结 相关文献 分组来给大家讲解相关原子类的常用api使用&#xff0c;不会全部都讲完&#xff0c;只是抽取几个比较经典的讲一下…

c++树(三)重心

目录 重心的基础概念 定义&#xff1a;使最大子树大小最小的点叫做树的重心 树的重心求解方式 例题&#xff1a; 重心的性质 性质1&#xff1a;重心点的最大子树大小不大于整棵树大小的一半。 性质1证明&#xff1a; 性质1的常用推导 推导1&#xff1a; 推导2&#x…

AI绘画SD中 ControlNet 组件 IP-Adapter 实现风格迁移,AI绘画垫图神器!

大家好&#xff0c;我是画画的小强 今天给大家介绍一下AI绘画SD中ControlNet 的 IP-Adapter 组件&#xff0c;该组件可以方便快捷的帮我们对图片的风格进行迁移&#xff0c;简而言之就是可以参考你放置的图片风格来生成其他图片。 它的效果和reference only有点类似&#xff…

了解网络是如何运作

“Web 的工作原理”提供了一个简化的视图,用于了解在计算机或手机上的 Web 浏览器中查看网页时发生的情况。 这个理论对于短期内编写 Web 代码来说并不是必需的,但不久之后,你就会真正开始从理解后台发生的事情中受益。 客户端和服务器 连接到 Internet 的计算机称为客户端和…