从0到1:多医院陪诊小程序开发笔记(上)

news2025/6/5 22:06:54

概要设计

医院陪诊预约小程序:随着移动互联网的普及,越来越多的医院陪诊服务开始向线上转型, 传统的预约方式往往效率低下,用户需耗费大量时间进行电话预约或现场排队,陪诊服务预约小程序集多种服务于一体,可以提高服务效率、提升用户体验。 用户可以浏览不同医院的位置、陪诊项目和陪诊时间,用户可以选择日期、时间段和人数,然后进行预约。

功能规划

本项目前后端完整代码包括公告通知,用户预约,签到核销, 管理者可以自定义预约要填写的内容,比如姓名、性别、年龄、诊疗科室、手机号等,后台预约管理,后台预约名单管理和导出Excel,后台设置预约时段和人数上线,后台管理最新通知公告,后台用户管理,后台医院管理,医院管理员设定等功能。

在这里插入图片描述

数据库设计

MeetModel.DB_STRUCTURE = {
	_pid: 'string|true',
	MEET_ID: 'string|true',
	MEET_ADMIN_ID: 'string|true|comment=添加的管理员',
	MEET_TITLE: 'string|true|comment=标题',

	MEET_UNIT_ID: 'string|false',
	MEET_UNIT_NAME: 'string|false',
 
	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_ADD_TIME: 'int|true',
	MEET_EDIT_TIME: 'int|true',
	MEET_ADD_IP: 'string|false',
	MEET_EDIT_IP: 'string|false',
};

JoinModel.DB_STRUCTURE = {
	_pid: 'string|true',
	JOIN_ID: 'string|true',

	JOIN_UNIT_ID: 'string|false',
	JOIN_UNIT_NAME: 'string|false',

	JOIN_EDIT_ADMIN_ID: 'string|false|comment=最近修改的管理员ID',
	JOIN_EDIT_ADMIN_NAME: 'string|false|comment=最近修改的管理员名',
	JOIN_EDIT_ADMIN_TIME: 'int|true|default=0|comment=管理员最近修改的时间',
	JOIN_EDIT_ADMIN_STATUS: 'int|false|comment=最近管理员修改为的状态 ',

	JOIN_IS_ADMIN: 'int|true|default=0|comment=是否管理员添加 0/1',

	JOIN_CODE: 'string|true|comment=核验码15位',
	JOIN_IS_CHECKIN: 'int|true|default=0|comment=是否核销 0/1 ',
	JOIN_CHECKIN_TIME: 'int|true|default=0',

	JOIN_USER_ID: 'string|true|comment=用户ID',
	JOIN_MEET_ID: 'string|true|comment=预约PK',
	JOIN_MEET_CATE_ID: 'string|true',
	JOIN_MEET_CATE_NAME: 'string|true',
	JOIN_MEET_TITLE: 'string|true|comment=项目',
	JOIN_MEET_DAY: 'string|true|comment=日期',
	JOIN_MEET_TIME_START: 'string|true|comment=时段开始',
	JOIN_MEET_TIME_END: 'string|true|comment=时段结束',
	JOIN_MEET_TIME_MARK: 'string|true|comment=时段标识',

	JOIN_COMPLETE_END_TIME: 'string|false|comment=完整结束时间',

	JOIN_START_TIME: 'int|true|comment=开始时间戳',

	JOIN_FORMS: 'array|true|default=[]|comment=表单',
	/* title:
	   mark:
	   type:
	   val:
	*/
	JOIN_OBJ: 'object|true|default={}',

	JOIN_STATUS: 'int|true|default=1|comment=状态 1=预约成功,10=已取消, 99=系统取消',

	JOIN_REASON: 'string|false|comment=审核拒绝或者取消理由',

	JOIN_ADD_TIME: 'int|true',
	JOIN_EDIT_TIME: 'int|true',
	JOIN_ADD_IP: 'string|false',
	JOIN_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);
	}

	/** 统一获取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);
	} 


	// 根据日期获取其所在天设置
	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;
		} else
			limitDesc = '人数不限制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(meetId) {

		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;


		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 = '预约结束';
						else
							throw ex;
					}
				}

				getTimes.push(timeNode);
			}
			dayNode.times = getTimes;

			getDaysSet.push(dayNode);
		}

		// 只返回需要的字段
		let ret = {};
		ret.MEET_DAYS_SET = getDaysSet;

		ret.MEET_QR = meet.MEET_QR;
		ret.MEET_TITLE = meet.MEET_TITLE;
		ret.MEET_CATE_NAME = meet.MEET_CATE_NAME;
		ret.MEET_OBJ = meet.MEET_OBJ;
		ret.MEET_UNIT_NAME = meet.MEET_UNIT_NAME;

		return ret;
	}


	/**  预约前获取关键信息 */
	async detailForJoin(userId, meetId, timeMark) {

		let fields = '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;
		else
			meet.myForms = [];

		return meet;
	}

	/** 按天获取预约项目 */
	async getMeetListByDay(unit, day) {
		let where = {
			'meet.MEET_STATUS': ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]],
			'day': day,
			DAY_UNIT_NAME: unit,
		};

		let orderBy = {
			'MEET_ORDER': 'asc',
			'MEET_ADD_TIME': 'desc'
		};

		let fields = 'meet.MEET_UNIT_NAME,meet.MEET_ORDER,meet.MEET_ADD_TIME,meet.MEET_TITLE,meet.MEET_DAYS_SET,meet.MEET_OBJ.cover, DAY_MEET_ID, day, times';

		let joinParams = {
			from: MeetModel.CL,
			localField: 'DAY_MEET_ID',
			foreignField: '_id',
			as: 'meet',
		};

		let list = await DayModel.getListJoin(joinParams, where, fields, orderBy, 1, 100, false);
		list = list.list;
		let retList = [];

		for (let k = 0; k < list.length; k++) {

			let usefulTimes = [];

			for (let j in list[k].times) {
				if (list[k].times[j].status != 1) continue;
				usefulTimes.push(list[k].times[j]);
			}

			if (usefulTimes.length == 0) continue;

			let node = {};
			node.timeDesc = usefulTimes.length > 1 ? usefulTimes.length + '个时段' : usefulTimes[0].start;
			node.title = list[k].meet.MEET_TITLE;
			node.pic = list[k].meet.MEET_OBJ.cover;
			node._id = list[k].DAY_MEET_ID;
			retList.push(node);

		}
		return retList;
	}

	/** 获取从某天开始可预约的日期 */
	async getHasDaysFromDay(unit, day) {
		console.log(unit)
		let where = {
			DAY_UNIT_NAME: unit,
			day: ['>=', day],
		};

		let fields = 'DAY_UNIT_NAME,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;
				}
			}
		} console.log(retList)
		return retList;
	}

	/** 取得预约分页列表 */
	async getMeetList({
		unit,
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		cateId, //分类查询条件
		page,
		size,
		isTotal = true,
		oldTotal
	}) {

		orderBy = orderBy || {
			'MEET_ORDER': 'asc',
			'MEET_ADD_TIME': 'desc'
		};
		let fields = 'MEET_UNIT_NAME,MEET_TITLE,MEET_OBJ,MEET_DAYS,MEET_CATE_NAME,MEET_CATE_ID';

		let where = {};
		where.and = {
			MEET_UNIT_NAME: unit,
			_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 已过期不能取消


		await JoinModel.del(where);


		// 统计
		this.statJoinCnt(join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK);

	}

	/** 取得我的预约详情 */
	async getMyJoinDetail(userId, joinId) {

		let fields = 'JOIN_UNIT_NAME,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
	}) {
		orderBy = orderBy || {
			//	'JOIN_MEET_DAY': 'desc',
			//	'JOIN_MEET_TIME_START': 'desc',
			'JOIN_ADD_TIME': 'desc'
		};
		let fields = 'JOIN_UNIT_NAME,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.CANCEL, JoinModel.STATUS.ADMIN_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下载

github下载

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

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

相关文章

建立连接后 TCP 请求卡住

大家读完觉得有意义记得关注和点赞&#xff01;&#xff01;&#xff01; 这篇文章描述了一个内核和BPF网络问题 以及故障排除步骤&#xff0c;这是一个值得深入研究的有趣案例 Linux 内核网络复杂性。 目录 1 故障报告 1.1 现象&#xff1a;概率健康检查失败 1.2 范围&am…

hive 笔记

1. 查看hive表的文件情况 搭建ui界面机器上查看 show create table xxx;得到文件地址 hdfs查看文件情况 hdfs dfs -ls hdfs://HDFS4005133/usr/hive/warehouse/xxx/xxxx/app_idxxx

无线通信模块简介

QuecPython 是运行在无线通信模块上的开发框架。对于首次接触物联网开发的用户而言&#xff0c;无线通信模块可能是一个相对陌生的概念。本文主要针对无线通信和蜂窝网络本身&#xff0c;以及模块的概念、特性和开发方式进行简要的介绍。 无线通信和蜂窝网络 物联网对无线通信…

把 CURSOR 的工具活动栏改成和 VSCODE 一样的左侧展示

目前使用cursor的时候发现工具栏与vscode的布局不一致&#xff0c;cursor在顶部导致操作起来不方便&#xff0c;如何改成与vscode相同的左侧布局展示。 解决方案 文件→首选项→设置&#xff0c;进入设置中&#xff0c;然后点击这个icon图标&#xff0c;可以打开配置文件 se…

碰一碰系统源码搭建==saas系统

搭建“碰一碰”系统&#xff08;通常指基于NFC或蓝牙的短距离交互功能&#xff09;的源码实现&#xff0c;需结合具体技术栈和功能需求。以下是关键步骤和示例代码&#xff1a; 技术选型 NFC模式&#xff1a;适用于Android/iOS设备的近场通信&#xff0c;需处理NDEF协议。蓝牙…

不加载PHP OpenTelemetry SDK实现Trace‌与Logs

目录 前言一、回到OpenTelemetry原理看问题1、数据接收&#xff08;Receivers&#xff09;2、数据处理&#xff08;Processors&#xff09;3、数据导出&#xff08;Exporters&#xff09; 二、不加载OpenTelemetry SDK实现Trace‌与Logs示例 前言 前面两篇我们分别介绍了OpenT…

Three.js搭建小米SU7三维汽车实战(6)颜色切换

颜色切换 接下来我们来实现懂车帝的颜色切换效果 可以让ai帮我们生成页面结构以及样式&#xff0c;注意changeCarBodyColor这个函数需要我们自己来写 // 创建颜色选择器UI function createColorSelector() {const colors [{ name: "深海蓝", hex: "#1A9CB0&qu…

mysql慢sql的实际处理方案之一

复习mysql架构图 当大批量慢sql过来&#xff0c;显然就是占用了线程池的链接&#xff0c;然后长久不释放&#xff0c;所以会出现线程池满的问题&#xff0c;致使正常业务sql也全部阻塞&#xff0c;影响整个业务。 AI搜索如下&#xff1a; 可以考虑一种方案&#xff1a; 将线…

4.2.3 Spark SQL 手动指定数据源

在本节实战中&#xff0c;我们学习了如何在Spark SQL中手动指定数据源以及如何使用format()和option()方法。通过案例演示&#xff0c;我们读取了不同格式的数据文件&#xff0c;包括CSV、JSON&#xff0c;并从JDBC数据源读取数据&#xff0c;展示了如何将这些数据转换为DataFr…

【论文解读】CVPR2023 PoseFormerV2:3D人体姿态估计(附论文地址)

论文链接&#xff1a;https://arxiv.org/pdf/2303.17472 源码链接&#xff1a;https://github.com/QitaoZhao/PoseFormerV2 Abstract 本文提出了 PoseFormerV2&#xff0c;通过探索频率域来提高 3D 人体姿态估计的效率和鲁棒性。PoseFormerV2 利用离散余弦变换&#xff08;DC…

Maven工程演示

软件&#xff1a;idea 一、项目创建 操作截图file -> New -> Projectnextnext -> Name:工程名称&#xff1b;Location:项目路径&#xff1b;项目创建完成;文件夹基本样例&#xff1a;&#xff08;如果不完整自己创建即可&#xff09;MANIFEST.MF内容 二、导入依赖 …

uniapp分包配置,uniapp设置subPackages

在使用uniapp开发过程中&#xff0c;由于项目比较大&#xff0c;无法直接上传&#xff0c;需要分包后才可以上传。 步骤&#xff1a; 1、在pages同级目录下创建分包的目录&#xff08;pages_second&#xff09;&#xff0c;把要分包的文件放到该目录下&#xff1b; 2、在pag…

C++八股 —— 手撕线程池

文章目录 一、背景二、线程池实现1. 任务队列和工作线程2. 构造和析构函数3. 添加任务函数4. 完整代码 三、阻塞队列实现1. 基础队列2. 升级版队列 四、测试代码五、相关问题六、其他实现方式 来自&#xff1a;华为C一面&#xff1a;手撕线程池_哔哩哔哩_bilibili 华为海思&am…

RPA如何支持跨平台和跨浏览器的自动化

RPA&#xff0c;即机器人流程自动化&#xff08;Robotic Process Automation&#xff09;&#xff0c;正日益成为企业实现业务流程高效自动化的关键技术。在复杂的数字化环境中&#xff0c;跨平台和跨浏览器的自动化需求极为迫切&#xff0c;RPA 通过多种技术手段和策略来满足这…

【笔记】Windows 成功部署 Suna 开源的通用人工智能代理项目部署日志

#工作记录 本地部署运行截图 kortix-ai/suna&#xff1a; Suna - 开源通用 AI 代理 项目概述 Suna 是一个完全开源的 AI 助手&#xff0c;通过自然对话帮助用户轻松完成研究、数据分析等日常任务。它结合了强大的功能和直观的界面&#xff0c;能够理解用户需求并提供结果。其强…

Linux531rsync定时同步 再回忆

rsync定时同步 环境配置 关闭防火墙&#xff0c;selinux systemctl stop firewalld systemctl disable firewall setenforce 0 cat /etc/selinux/configpei SELINUXdisable设置主机名 systemctl set-hostname code systemctl set-hostname backup设置静态IP rsync由于要设…

【KWDB 创作者计划】_探秘浪潮KWDB数据库:从时间索引到前沿技术

探秘浪潮KWDB数据库&#xff1a;从时间索引到前沿技术 文章目录 探秘浪潮KWDB数据库&#xff1a;从时间索引到前沿技术引言1.浪潮KWDB数据库时间索引深度解析1.1时间索引工作原理1.2时间索引创建与管理实践 2.浪潮KWDB数据库前沿产品技术纵览2.1多模融合存储引擎2.2就地计算技术…

安卓逆向篇LSP 模块HOOK 添加技术绕过检测算法解密逻辑验证

前置解释&#xff1a; 0 、 Magisk &#xff1a; 是当前 Android 社区用来获取 root 权限的主流方式开源工具 1 、 LSP 框架&#xff1a; XPosed 框架因只支持安卓 8 及以下&#xff0c;故高版本应使用 MagiskLSPosed 2 、 HOOK 技术&#xff1a; 钩子技术&…

第一节 51单片机概述

目录 一、单片机系统组成 &#xff08;一&#xff09;、单片机硬件系统 &#xff08;二&#xff09;单片机的软件系统 二、STC89C52单片机 &#xff08;1&#xff09;、基本信息 &#xff08;2&#xff09;、命名规则 &#xff08;3&#xff09;、单片机内部结构图 &am…

Google car key:安全、便捷的汽车解锁新选择

有了兼容的汽车和 Android 手机&#xff0c;Google car key可让您将Android 手机用作车钥匙。您可以通过兼容的 Android 手机锁定、解锁、启动汽车并执行更多功能。但是&#xff0c;Google car key安全吗&#xff1f;它是如何工作的&#xff1f;如果我的手机电池没电了怎么办&a…