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

06-01 1205阅读

概要设计

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

功能规划

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

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

数据库设计

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 = ['=', startDay];
		let orderBy = {
			'day': 'asc'
		}
		let list = await DayModel.getAllBig(where, 'day,dayDesc,times', orderBy, 1000);
		for (let k = 0; k = timeSet.limit) {
				this.AppError('该时段预约人员已满,请选择其他');
			}
			let maxCnt = timeSet.limit - timeSet.stat.succCnt;
			if (maxCnt = 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, `#预约截止规则,预约日期=`, `预约时段=[${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 = 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  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  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')];
					//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设计

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

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

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

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

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

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

后台管理系统设计

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

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

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

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

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

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

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

git下载

github下载

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码