【SSM】医疗健康平台-用户端-体检预约

知识目标

  • 了解FreeMarker,能够简述FreeMarker的作用和生成文件的原理

  • 熟悉FreeMarker的常用指令,能够在FTL标签中正确使用assign指令、include指令、if指令和list指令

  • 掌握显示套餐列表功能的实现

  • 掌握显示套餐详情功能的实现

  • 掌握体检预约功能的实现

  • 掌握静态页面的实现方式,能够使用FreeMarker技术实现套餐列表与套餐详情页面的静态化

体检是了解自身健康状况、及时发现身体异常,以及预防疾病的重要手段之一。为了给广大体检用户提供便利,医疗健康用户端设立了线上体检预约服务,用户可以随时随地进行体检预约。医疗健康的用户端可以展示套餐列表、套餐详情和进行体检预约。接下来,本模块将对用户端的体检预约进行详细讲解。

8-1 套餐列表

体检套餐是医疗健康面向体检用户销售的产品,为了让用户全方位地了解体检套餐的种类,医疗健康用 户端提供套餐列表页面供用户浏览。

在浏览器中访问用户端首页 index.html。

在用户端首页单击“体检预约”超链接后会跳转到套餐列表页面 setmeal.html,该页面以列表的形式展示所有的体检套餐。

(1)提交查询所有套餐的请求

要实现在访问setmeal.html页面时展示所有的套餐,可以将查询的操作定义在钩子函数created()中,在created()函数中使用Axios发送异步请求的方式获取所有的套餐数据。

(2)实现查询套餐控制器

在 controller 包下的控制器类 SetmealController,在类中定义 getSetmeal( )方法,用于接收和处理查询所有套餐的请求。

//获取所有套餐@RequestMapping("/getSetmeal")public Result getSetmeal() {try {List<Setmeal> list = setmealService.findAll();return new Result(true,MessageConstant.QUERY_SETMEAL_SUCCESS, list);} catch (Exception e) {e.printStackTrace();return new Result(false, MessageConstant.QUERY_SETMEAL_FAIL);}}

(3)创建查询套餐服务

在SetmealService接口中定义findAll()方法,用于查询套餐列表。

//查询所有套餐List<Setmeal> findAll();

(4)实现查询套餐服务

在SetmealServiceImpl类中重写接口的findAll()方法,用于查询套餐列表。

//查询所有套餐@Overridepublic List<Setmeal> findAll() {return setmealDao.findAll();}

(5)实现持久层查询套餐

在SetmealDao接口中定义findAll()方法,用于查询所有套餐。

List<Setmeal> findAll();    //获取所有套餐信息

在SetmealDao.xml映射文件中使用select元素映射查询语句,查询所有的套餐数据。

<!--获取所有套餐信息--><select id="findAll" resultType="com.health.entity.Setmeal">SELECT * FROM t_setmeal</select>

(6)测试套餐列表功能

重启服务器,访问客户端预约体检页面http://localhost:8080/mobile/pages/setmeal.html

8-2 套餐详情

用户想要了解套餐的所有检查组和检查项,可以访问套餐详情页面 setmeal_detail.html,在页面上查看套餐的详细信息。

(1)提交查询套餐详细信息的请求

将查询的操作定义在钩子函数 created( )中,在 created( )函数中通过使用 Axios 发送异步请求的方式查询套餐详细信息。

<script src="../plugins/vue/axios-0.18.0.js"></script>
<script>var vue = new Vue({......created(){axios.post("/setmeal/findById_m.do?id=" + id).then((response) => {if(response.data.flag){this.setmeal = response.data.data;this.imgUrl = 'http://sfaztaij0.hd-bkt.clouddn.com/' + this.setmeal.img;}});}});
</script>

(2)实现查询套餐详细信息控制器

在 SetmealController 类中定义 findById( )方法,用于接收和处理查询套餐详细信息的请求。

//根据套餐id查询套餐详情,包含(套餐基本信息、套餐关联的检查组、检查组关联的检查项)@RequestMapping("/findById_m")public Result findById_m(Integer id) {try {//根据套餐id查询套餐详情Setmeal setmeal = setmealService.findSetmealById(id);return new Result(true,MessageConstant.QUERY_SETMEAL_SUCCESS, setmeal);} catch (Exception e) {e.printStackTrace();return new Result(false, MessageConstant.QUERY_SETMEAL_FAIL);}}

(3)创建查询套餐详细信息服务

在 SetmealService 接口中定义 findSetmealById( )方法,用于根据套餐 id 查询套餐详细信息。

Setmeal findSetmealById(Integer id);//根据id查询套餐详情

(4)实现查询套餐详细信息服务

在 SetmealServiceImpl 类中重写 SetmealService 接口的 findSetmealById( )方法,用于根据套餐 id 查询套餐详细信息。

//根据id查询套餐详情public Setmeal findSetmealById(Integer id) {return setmealDao.findById4Detail(id);}

(5)实现持久层查询套餐详细信息

在 SetmealDao 接口中定义 findById4Detail( )方法,用于根据套餐 id 查询套餐详细信息。

Setmeal findById4Detail(Integer id);    //查询套餐详情

在 SetmealDao.xml 映射文件中使用select元素映射查询语句,根据套餐id 查询套餐详细信息。

 <resultMap id="baseResultMap" type="com.health.entity.Setmeal"><id column="id" property="id"/><result column="name" property="name"/><result column="code" property="code"/><result column="helpCode" property="helpCode"/><result column="sex" property="sex"/><result column="age" property="age"/><result column="price" property="price"/><result column="remark" property="remark"/><result column="attention" property="attention"/><result column="img" property="img"/></resultMap><!--配置多对多映射关系--><resultMap id="findByIdResultMap" extends="baseResultMap"type="com.health.entity.Setmeal"><!--column用于指定将哪个字段的值传递给第二条sql--><collection property="checkGroups"ofType="com.health.entity.CheckGroup"column="id"select="com.health.dao.CheckGroupDao.selectCheckGroupsBySetmealId"></collection></resultMap><!--根据套餐id查询套餐详情(包含基本信息、关联的检查组、检查项信息)--><select id="findById4Detail" parameterType="int"resultMap="findByIdResultMap">SELECT * FROM t_setmeal WHERE id = #{id}</select>

在CheckGroupDao.xml 映射文件中使用select元素映射查询语句,根据套餐 id 查询套餐对检查组的引用。

<resultMap id="baseResultMap" type="com.health.entity.CheckGroup"><id column="id" property="id"/><result column="name" property="name"/><result column="code" property="code"/><result column="helpCode" property="helpCode"/><result column="sex" property="sex"/><result column="remark" property="remark"/><result column="attention" property="attention"/></resultMap><!--配置多对多映射关系--><resultMap id="findByIdResultMap" extends="baseResultMap"type="com.health.entity.CheckGroup"><collection property="checkItems" ofType="com.health.entity.CheckItem"column="id"select="com.health.dao.CheckItemDao.findCheckItemsByCheckGroupId"></collection></resultMap><!--根据套餐id查询关联的检查组集合--><select id="selectCheckGroupsBySetmealId" parameterType="int"resultMap="findByIdResultMap">SELECT * FROM t_checkgroup WHERE id IN(SELECT checkgroup_id FROM t_setmeal_checkgroupWHERE setmeal_id = #{setmealId})</select>

在CheckItemDao.xml 映射文件中使用select元素映射查询语句,根据检查组 id 查询检查组对检查项的引用。

<!--根据检查组id查询关联的检查项--><select id="findCheckItemsByCheckGroupId" parameterType="int"resultType="com.health.entity.CheckItem">SELECT * FROM t_checkitem WHERE id IN(SELECT checkitem_id FROM t_checkgroup_checkitemWHERE checkgroup_id = #{checkgroup_id})</select>

(6)测试套餐详情功能

重启服务器,在浏览器中访问 http://localhost:8080/mobile/pages/setmeal.html。单击名称为肝肾检查的套餐,跳转到套餐详情页面。

8-3 体检预约

用户选择好体检套餐后,单击套餐详情页面的“立即预约”进入对应的体检预约页面 orderInfo.html。

在体检预约页面中,输入体检人信息后,单击“提交预约”按钮,如果预约失败,在 orderInfo.html 页面会提示失败原因;如果预约成功,跳转到预约成功页面 orderSuccess.html。

体检预约可以分解成 4 个功能,分别是跳转到体检预约页面后显示套餐、发送短信验证码、预约体检、跳转到预约成功页面。

1.跳转体检预约页面后显示套餐

2.发送短信验证码

为 orderInfo.html 页面的“发送验证码”绑定单击事件,在单击事件触发后提交填写的手机号。由 ValidateCodeController类的 send4Order( )方法接收页面提交的手机号,并调用短信服务SMSUtils 发送短信验证码。

3.预约体检

4.跳转到预约成功页面

1.跳转到体检预约页面后显示套餐

(1)提交跳转到体检预约页面的请求

为“立即预约”绑定单击事件,并设置单击时要调用的方法,在方法中提交跳转页面的请求。

<div class="box-button"><a class="order-btn" @click="toOrderInfo()">立即预约</a>
</div>

在 setmeal_detail.html 页面中定义 toOrderInfo( )方法,用于提交跳转页面的请求。

<script>var vue = new Vue({......methods:{toOrderInfo(){window.location.href = "orderInfo.html?id=" + id;}}});
</script>

(2)提交查询套餐的请求

要实现访问orderInfo.html页面时展示套餐信息,可以将查询套餐的操作定义在钩子函数created()中。

<script src="../plugins/vue/axios-0.18.0.js"></script>
<script>created(){//发送axios请求,根据套餐id查询套餐信息,用于页面展示axios.post("/setmeal/findById_m.do?id=" + id).then((response)=>{if(response.data.flag){this.setmeal = response.data.data;}});},......
</script>

(3)实现查询套餐信息

接下来应该实现查询套餐信息的后台逻辑代码,但由于查询套餐信息的 findById( )方法在套餐详情中已经实现,所以这里不再重复展示,稍后直接进行功能测试即可。

(4)测试跳转到体检预约页面后显示套餐

重启服务器,在浏览器中访问 http://localhost:8080/mobile/pages/setmeal_detail.html?id=6 进入肝肾检查的套餐详情页面,单击“立即预约”跳转到对应的体检预约页面。

2.发送短信验证码

(1)提交发送短信验证码的请求

要实现单击“发送验证码”按钮后接收短信验证码,可以为“发送验证码”按钮绑定单击事件,并设置单击时要调用的方法,在该方法中提交发送验证码的请求。

<input style="font-size: x-small;" id="validateCodeButton" @click="sendValidateCode()" type="button" value="发送验证码">

orderInfo.html页面中定义sendValidateCode()方法,用于发送短信验证码

sendValidateCode() {var telephone = this.orderInfo.telephone;//获取用户输入的手机号if (!checkTelephone(telephone)) {this.$message.error("手机号输入错误,请检查后重新输入!");return false;}validateCodeButton = $("#validateCodeButton")[0];//锁定按钮clock = window.setInterval(doLoop, 1000);//使用定时器方法每隔1秒执行一次axios.post("/validatecode/send4Order.do?telephone=" + telephone).then((res) => {if (!res.data.flag) {this.$message.error(res.data.message);}});
}

(2)实现发送短信验证码控制器

在 ValidateCodeController 类中定义 send4Order( )方法,用于接收和处理发送短信验证码的请求。

//体检预约发送验证码@RequestMapping("/send4Order")public Result send4Order(String telephone,HttpSession session){String code = ValidateCodeUtils.generateValidateCode(4).toString();System.out.println("验证码:" + code);session.setAttribute("phone",telephone);session.setAttribute("code",code);try{SMSUtils.sendShortMessage(telephone,code);return new Result(true, MessageConstant.SEND_VALIDATECODE_SUCCESS);}catch (Exception e){e.printStackTrace();return new Result(false, MessageConstant.SEND_VALIDATECODE_FAIL);}}

(3)测试发送短信验证码

重启服务器,在浏览器中访问http://localhost:8080/mobile/pages/orderInfo.html?id=6。填写手机号后,单击“发送验证码”,通过手机接收短信验证码后将验证码填写到输入框中。

3.预约体检

(1)提交预约请求

要实现单击“提交预约”按钮时提交预约请求,可以为该按钮绑定单击事件,并设置单击时要调用的方法,在该方法中提交预约请求。

<button type="button" class="btn order-btn" @click="submitOrder()">提交预约</button>

在 orderInfo.html 页面定义 submitOrder( )方法,用于提交预约请求。

submitOrder() {var idCard = this.orderInfo.idCard;//身份证进行校验if (!checkIdCard(idCard)) {this.$message.error("身份证号输入错误,请检查后重新输入!");return false;}axios.post("/order/submitOrder.do", this.orderInfo).then((res) => {if (res.data.flag) {window.location.href = "orderSuccess.html?orderId=" + res.data.data;} else {this.$message.error(res.data.message);//预约失败,弹出提示}});
}

(2)创建预约类

在 entity 包下创建 Order 类,在类中声明预约的属性,定义各属性的getter/setter 方法,并定义构造方法。

package com.health.entity;import java.io.Serializable;
import java.util.Date;/*** 体检预约信息*/
public class Order implements Serializable {public static final String ORDERTYPE_TELEPHONE = "电话预约";public static final String ORDERTYPE_CLIENT = "客户端预约";public static final String ORDERSTATUS_YES = "已到诊";public static final String ORDERSTATUS_NO = "未到诊";private Integer id;private Integer memberId;//会员idprivate Date orderDate;//预约日期private String orderType;//预约类型 电话预约/客户端预约private String orderStatus;//预约状态(是否到诊)private Integer setmealId;//体检套餐idpublic Order() {}public Order(Integer id) {this.id = id;}public Order(Integer memberId, Date orderDate, Integer setmealId) {this.memberId = memberId;this.orderDate = orderDate;this.setmealId = setmealId;}public Order(Integer memberId, Date orderDate, String orderType, String orderStatus, Integer setmealId) {this.memberId = memberId;this.orderDate = orderDate;this.orderType = orderType;this.orderStatus = orderStatus;this.setmealId = setmealId;}public Order(Integer id, Integer memberId, Date orderDate, String orderType, String orderStatus, Integer setmealId) {this.id = id;this.memberId = memberId;this.orderDate = orderDate;this.orderType = orderType;this.orderStatus = orderStatus;this.setmealId = setmealId;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Integer getMemberId() {return memberId;}public void setMemberId(Integer memberId) {this.memberId = memberId;}public Date getOrderDate() {return orderDate;}public void setOrderDate(Date orderDate) {this.orderDate = orderDate;}public String getOrderType() {return orderType;}public void setOrderType(String orderType) {this.orderType = orderType;}public String getOrderStatus() {return orderStatus;}public void setOrderStatus(String orderStatus) {this.orderStatus = orderStatus;}public Integer getSetmealId() {return setmealId;}public void setSetmealId(Integer setmealId) {this.setmealId = setmealId;}
}

(3)导入公共资源

在 utils 包中导入 DateUtils 类,用于日期格式化操作。

package com.health.utils;import java.text.SimpleDateFormat;
import java.util.*;/*** 日期操作工具类*/
public class DateUtils {/*** 日期转换-  String -> Date** @param dateString 字符串时间* @return Date类型信息* @throws Exception 抛出异常*/public static Date parseString2Date(String dateString) throws Exception {if (dateString == null) {return null;}return parseString2Date(dateString, "yyyy-MM-dd");}/*** 日期转换-  String -> Date** @param dateString 字符串时间* @param pattern    格式模板* @return Date类型信息* @throws Exception 抛出异常*/public static Date parseString2Date(String dateString, String pattern) throws Exception {if (dateString == null) {return null;}SimpleDateFormat sdf = new SimpleDateFormat(pattern);Date date = sdf.parse(dateString);return date;}/*** 日期转换 Date -> String** @param date Date类型信息* @return 字符串时间* @throws Exception 抛出异常*/public static String parseDate2String(Date date) throws Exception {if (date == null) {return null;}return parseDate2String(date, "yyyy-MM-dd");}/*** 日期转换 Date -> String** @param date    Date类型信息* @param pattern 格式模板* @return 字符串时间* @throws Exception 抛出异常*/public static String parseDate2String(Date date, String pattern) throws Exception {if (date == null) {return null;}SimpleDateFormat sdf = new SimpleDateFormat(pattern);String strDate = sdf.format(date);return strDate;}/*** 获取当前日期的本周一是几号** @return 本周一的日期*/public static Date getThisWeekMonday() {Calendar cal = Calendar.getInstance();cal.setTime(new Date());// 获得当前日期是一个星期的第几天int dayWeek = cal.get(Calendar.DAY_OF_WEEK);if (1 == dayWeek) {cal.add(Calendar.DAY_OF_MONTH, -1);}// 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一cal.setFirstDayOfWeek(Calendar.MONDAY);// 获得当前日期是一个星期的第几天int day = cal.get(Calendar.DAY_OF_WEEK);// 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day);return cal.getTime();}/*** 获取当前日期周的最后一天** @return 当前日期周的最后一天*/public static Date getSundayOfThisWeek() {Calendar c = Calendar.getInstance();int dayOfWeek = c.get(Calendar.DAY_OF_WEEK) - 1;if (dayOfWeek == 0) {dayOfWeek = 7;}c.add(Calendar.DATE, -dayOfWeek + 7);return c.getTime();}/*** 根据日期区间获取月份列表** @param minDate 开始时间* @param maxDate 结束时间* @return 月份列表* @throws Exception*/public static List<String> getMonthBetween(String minDate, String maxDate, String format) throws Exception {ArrayList<String> result = new ArrayList<>();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");Calendar min = Calendar.getInstance();Calendar max = Calendar.getInstance();min.setTime(sdf.parse(minDate));min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1);max.setTime(sdf.parse(maxDate));max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2);SimpleDateFormat sdf2 = new SimpleDateFormat(format);Calendar curr = min;while (curr.before(max)) {result.add(sdf2.format(curr.getTime()));curr.add(Calendar.MONTH, 1);}return result;}/*** 根据日期获取年度中的周索引** @param date 日期* @return 周索引* @throws Exception*/public static Integer getWeekOfYear(String date) throws Exception {Date useDate = parseString2Date(date);Calendar cal = Calendar.getInstance();cal.setTime(useDate);return cal.get(Calendar.WEEK_OF_YEAR);}/*** 根据年份获取年中周列表** @param year 年分* @return 周列表* @throws Exception*/public static Map<Integer, String> getWeeksOfYear(String year) throws Exception {Date useDate = parseString2Date(year, "yyyy");Calendar cal = Calendar.getInstance();cal.setTime(useDate);//获取年中周数量int weeksCount = cal.getWeeksInWeekYear();Map<Integer, String> mapWeeks = new HashMap<>(55);for (int i = 0; i < weeksCount; i++) {cal.get(Calendar.DAY_OF_YEAR);mapWeeks.put(i + 1, parseDate2String(getFirstDayOfWeek(cal.get(Calendar.YEAR), i)));}return mapWeeks;}/*** 获取某年的第几周的开始日期** @param year 年分* @param week 周索引* @return 开始日期* @throws Exception*/public static Date getFirstDayOfWeek(int year, int week) throws Exception {Calendar c = new GregorianCalendar();c.set(Calendar.YEAR, year);c.set(Calendar.MONTH, Calendar.JANUARY);c.set(Calendar.DATE, 1);Calendar cal = (GregorianCalendar) c.clone();cal.add(Calendar.DATE, week * 7);return getFirstDayOfWeek(cal.getTime());}/*** 获取某年的第几周的结束日期** @param year 年份* @param week 周索引* @return 结束日期* @throws Exception*/public static Date getLastDayOfWeek(int year, int week) throws Exception {Calendar c = new GregorianCalendar();c.set(Calendar.YEAR, year);c.set(Calendar.MONTH, Calendar.JANUARY);c.set(Calendar.DATE, 1);Calendar cal = (GregorianCalendar) c.clone();cal.add(Calendar.DATE, week * 7);return getLastDayOfWeek(cal.getTime());}/*** 获取当前时间所在周的开始日期** @param date 当前时间* @return 开始时间*/public static Date getFirstDayOfWeek(Date date) {Calendar c = new GregorianCalendar();c.setFirstDayOfWeek(Calendar.SUNDAY);c.setTime(date);c.set(Calendar.DAY_OF_WEEK, c.getFirstDayOfWeek());return c.getTime();}/*** 获取当前时间所在周的结束日期** @param date 当前时间* @return 结束日期*/public static Date getLastDayOfWeek(Date date) {Calendar c = new GregorianCalendar();c.setFirstDayOfWeek(Calendar.SUNDAY);c.setTime(date);c.set(Calendar.DAY_OF_WEEK, c.getFirstDayOfWeek() + 6);return c.getTime();}//获得上周一的日期public static Date geLastWeekMonday(Date date) {Calendar cal = Calendar.getInstance();cal.setTime(getThisWeekMonday(date));cal.add(Calendar.DATE, -7);return cal.getTime();}//获得本周一的日期public static Date getThisWeekMonday(Date date) {Calendar cal = Calendar.getInstance();cal.setTime(date);// 获得当前日期是一个星期的第几天int dayWeek = cal.get(Calendar.DAY_OF_WEEK);if (1 == dayWeek) {cal.add(Calendar.DAY_OF_MONTH, -1);}// 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一cal.setFirstDayOfWeek(Calendar.MONDAY);// 获得当前日期是一个星期的第几天int day = cal.get(Calendar.DAY_OF_WEEK);// 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day);return cal.getTime();}//获得下周一的日期public static Date getNextWeekMonday(Date date) {Calendar cal = Calendar.getInstance();cal.setTime(getThisWeekMonday(date));cal.add(Calendar.DATE, 7);return cal.getTime();}//获得今天日期public static Date getToday(){return new Date();}//获得本月一日的日期public static Date getFirstDay4ThisMonth(){Calendar calendar = Calendar.getInstance();calendar.set(Calendar.DAY_OF_MONTH,1);return calendar.getTime();}public static void main(String[] args) {try {System.out.println("本周一" + parseDate2String(getThisWeekMonday()));System.out.println("本月一日" + parseDate2String(getFirstDay4ThisMonth()));} catch (Exception e) {e.printStackTrace();}}
}

(4)实现预约体检控制器

在 controller 包下创建控制器类 OrderController,在类中定义submitOrder( )方法,用于接收和处理预约体检的请求。

(1)获取用户输入的验证码与存储在session中的验证码进行比对,判断是否正确;

(2)验证码比对通过后,调用OrderService接口的order()方法实现预约体检。

/*** 体检预约*/
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;//提交体检预约请求@RequestMapping("/submitOrder")public Result submitOrder(@RequestBody Map map, HttpSession session){String telephone = (String) map.get("telephone");//获取用户页面输入的验证码String validateCode = (String) map.get("validateCode");//从session获取保存的验证码Object code=session.getAttribute("code");//校验用户输入的验证码是否正确if(code == null || !String.valueOf(code).equals(validateCode)){//验证码输入错误return new Result(false, MessageConstant.VALIDATECODE_ERROR);}//通过dubbo调用服务实现预约逻辑Result result = null;try{map.put("orderType", Order.ORDERTYPE_CLIENT);//预约类型result = orderService.order(map);//调用服务}catch (Exception e){e.printStackTrace();return result;}return result;}
}

(5)创建预约体检服务

在 service 包下创建接口 OrderService,在接口中定义 order( )方法,用于预约体检。

/*** 体检预约接口*/
public interface OrderService {Result order(Map map)throws Exception;//体检预约
}

(6)实现预约体检服务

在service.impl 包下创建 OrderService 接口的实现类OrderServiceImpl,在类中重写 order( )方法,用于预约体检。

(1)查询用户是否在某日期进行过体验预约并判断该日期是否已经约满。

(2)查询当前用户是否为会员,如果是会员,查询该会员是否重复预约,若是重复预约则无法继续预约;如果不是会员,则将该用户自动注册为会员。

(3)新增预约,更新已预约人数。

/*** 体检预约服务接口实现类*/
@Service("orderService")
@Transactional
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderDao orderDao;@Autowiredprivate OrderSettingDao orderSettingDao;@Autowiredprivate MemberDao memberDao;//体检预约public Result order(Map map) throws Exception{//获取日期String orderDate = (String) map.get("orderDate");Date date = DateUtils.parseString2Date(orderDate);//根据日期查询预约设置信息OrderSetting orderSetting = orderSettingDao.findByOrderDate(date);if(orderSetting == null){//所选日期没有提前进行预约设置,不能完成预约return new Result(false,MessageConstant.SELECTED_DATE_CANNOT_ORDER);}if(orderSetting.getReservations() >= orderSetting.getNumber()){//所选日期已经约满,无法预约return new Result(false, MessageConstant.ORDER_FULL);}//判断是否在重复预约String telephone = (String) map.get("telephone");Member member = memberDao.findByTelephone(telephone);if(member != null){Integer memberId = member.getId();//会员idInteger setmealId =Integer.parseInt((String)map.get("setmealId"));//套餐idOrder order = new Order(memberId,date,setmealId);List<Order> orderList = orderDao.findByCondition(order);if(orderList != null && orderList.size() > 0){//用户在重复预约,不能完成预约return new Result(false,MessageConstant.HAS_ORDERED);}}if(member == null){//当前用户不是会员,需要自动完成注册member = new Member();member.setName((String) map.get("name"));member.setPhoneNumber(telephone);member.setIdCard((String) map.get("idCard"));member.setSex((String) map.get("sex"));member.setRegTime(new Date());memberDao.add(member);}//保存预约信息Order order = new Order(member.getId(),date,(String)map.get("orderType"),Order.ORDERSTATUS_NO,Integer.parseInt((String) map.get("setmealId")));orderDao.add(order);//更新已预约人数orderSetting.setReservations(orderSetting.getReservations() + 1);orderSettingDao.editReservationsByOrderDate(orderSetting);return new Result(true,MessageConstant.ORDER_SUCCESS,order.getId());}
}

(7)实现持久层预约体检

在 dao 包下创建持久层接口 OrderDao,用于处理与体检预约相关的操作。

/*** 体检预约持久层接口*/
public interface OrderDao {public List<Order> findByCondition(Order order);//查询预约记录public void add(Order order);//新增预约
}

在dao 目录下创建与 OrderDao 接口同名的映射文件 OrderDao.xml。在文件中使用insert 元素映射新增语句,使用select元素映射查询语句。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.health.dao.OrderDao" ><resultMap id="baseResultMap" type="com.health.entity.Order"><id column="id" property="id"/><result column="member_id" property="memberId"/><result column="orderDate" property="orderDate"/><result column="orderType" property="orderType"/><result column="orderStatus" property="orderStatus"/><result column="setmeal_id" property="setmealId"/></resultMap><!--新增--><insert id="add" parameterType="com.health.entity.Order"><selectKey resultType="java.lang.Integer" order="AFTER"keyProperty="id">SELECT LAST_INSERT_ID()</selectKey>INSERT INTO t_order(member_id,orderDate,orderType,orderStatus,setmeal_id)VALUES (#{memberId},#{orderDate},#{orderType},#{orderStatus},#{setmealId})</insert><!--动态条件查询--><select id="findByCondition" parameterType="com.health.entity.Order"resultMap="baseResultMap">SELECT * FROM t_order<where><if test="id != null">AND id = #{id}</if><if test="memberId != null">AND member_id = #{memberId}</if><if test="orderDate != null">AND orderDate = #{orderDate}</if><if test="orderType != null">AND orderType = #{orderType}</if><if test="orderStatus != null">AND orderStatus = #{orderStatus}</if><if test="setmealId != null">AND setmeal_id = #{setmealId}</if></where></select></mapper>

在OrderSettingDao接口中定义findByOrderDate()方法和editReservationsByOrderDate( )方法。

//根据日期查询预约设置信息OrderSetting findByOrderDate(Date date);//更新已预约人数void editReservationsByOrderDate(OrderSetting orderSetting);

在 OrderSettingDao.xml 映射文件中使用select元素映射查询语句,使用update元素映射更新语句。

<!--根据日期查询预约设置信息--><select id="findByOrderDate" parameterType="date" resultType="com.health.entity.OrderSetting">SELECT * FROM t_ordersetting WHERE orderDate = #{orderDate}</select><!--更新已预约人数--><update id="editReservationsByOrderDate" parameterType="com.health.entity.OrderSetting">UPDATE t_ordersetting SET reservations = #{reservations}WHERE orderDate = #{orderDate}</update>

(8)查询体检预约信息表 t_order

通过查询体检预约信息表 t_order 中的数据验证新增预约的结果。

CREATE TABLE `health`.`t_order`( `id` INT NOT NULL AUTO_INCREMENT, `member_id` INT, `orderDate` DATE, `orderType` VARCHAR(100), `orderStatus` VARCHAR(100), `setmeal_id` INT, PRIMARY KEY (`id`) ); 

4.跳转到预约成功页面

(1)提交跳转到预约成功页面的请求

要实现跳转到 orderSuccess.html 页面之后展示预约信息,可以将查询预约信息的操作定义在 Vue 提供的钩子函数 created( )中,created( )函数在 Vue 对象初始化完成后自动执行。

<script src="../plugins/vue/axios-0.18.0.js"></script>
<script>var vue = new Vue({......created(){axios.post("/order/findById.do?id=" + id).then((response) => {this.orderInfo = response.data.data;});}});
</script>

(2)实现查询预约控制器

在 OrderController 类中定义 findById( )方法,用于接收和处理根据预约 id 查询预约信息的请求。

//根据预约id查询预约信息@RequestMapping("/findById")public Result findById(Integer id){try{Map map = orderService.findById(id);return new Result(true,MessageConstant.QUERY_ORDER_SUCCESS,map);}catch (Exception e){e.printStackTrace();return new Result(false,MessageConstant.QUERY_ORDER_FAIL);}}

(3)创建查询预约服务

在 OrderService 接口中定义 findById( )方法,用于根据预约 id 查询预约信息。

Map findById(Integer id);//根据预约id查询预约信息

(4)实现查询预约服务

在 OrderServiceImpl 类中重写 OrderService 接口的findById( )方法,用于根据预约 id 查询预约信息。

    //根据id查询预约详细信息(包括会员姓名、套餐名称、预约基本信息)public Map findById(Integer id) {Map map = orderDao.findById4Detail(id);if(map != null){//处理日期格式Date orderDate = (Date) map.get("orderDate");try {map.put("orderDate",DateUtils.parseDate2String(orderDate));} catch (Exception e) {e.printStackTrace();}}return map;}

(5)实现持久层查询预约信息

在 OrderDao 接口中定义 findById4Detail( )方法,用于根据预约 id 查询预约信息。

Map findById4Detail(Integer id);//查询预约信息

在 OrderDao.xml 映射文件中使用select元素映射查询语句,查询预约信息,包括体检人信息和套餐信息。

<!--根据预约id查询预约信息,包括体检人信息、套餐信息--><select id="findById4Detail" parameterType="int" resultType="map">SELECT m.name AS member,s.name AS setmeal,o.orderDate AS orderDate,o.orderType AS orderTypeFROM t_order o, t_member m, t_setmeal sWHERE o.member_id=m.id AND o.setmeal_id=s.id AND o.id=#{id}</select>

(6)测试跳转到预约成功页面

重启服务器,在浏览器中访问 http://localhost:8080/mobile/pages/orderSuccess.html?orderId=22。

8-4 页面静态化(可略)

用户登录用户端进行体检预约时,需要访问套餐列表页面和套餐详情页面,此时访问这两个页面,页面展示的所有信息都需要从数据库中查询,如果访问量大,会造成数据库的访问压力大、页面刷新缓慢等问题。 从套餐包含的信息可以看出,套餐包含基本信息、对检查组的引用信息。一般情况下套餐内容变化频率不高,所以我们可以将套餐列表页面和套餐详情页面动态查询的结果分别转化成固定的静态页面进行展示,从而为数据库减压并提高系统运行性能。

页面静态化就是将原来的动态网页使用静态化技术生成静态网页,这样用户在访问网页时,服务器直接响应静态页面,不需要反复查询数据库,从而有效降低数据库的访问压力。 与数据库中数据保持一致的静态页面才是有效可用的。当管理端执行套餐新增、编辑或删除的操作后,会改变套餐的信息,这时需要重新生成静态页面。

 

FreeMarker概述

FreeMarker是一款用Java语言编写的模板引擎,是一种基于模板和要改变的数据生成输出文本的通用工具。例如,生成HTML页面、配置文件、源代码等。它不是面向最终用户的,而是一个Java类库,是一款可以嵌入其他产品的组件。

Template指的是模板;Java objects指的是准备数据;Output指的是最终的文件。通过FreeMarker将数据填充到模板中,然后通过Output进行输出,最终生成静态文件。

 

FreeMarker 模板的开发语言是 FreeMarker Template Language(FreeMarker 模板语言,下文简称 FTL),FTL的基本语法由文本、插值、FTL 标签和注释组成,具体如下。 文本:文本会按原样输出。 插值:这部分的输出会被计算的值替换。插值由 ${ and } (或者 #{ and })分隔。 FTL 标签:FTL 标签与 HTML 标签相似,用于给 FreeMarker 指示,不会在输出内容中显示。 注释:其注释与 HTML 的注释也很相似,是由 <#-- 和 -->来分隔的。其注释会被 FreeMarker 直接忽略,不会在输出内容中显示。

FreeMarker 指令

FreeMarker 指令通过 FTL 标签调用,FreeMarker 标签的语法与 HTML、XML 标签的语法类似,为了对FreeMarker 标签和 HTML、XML 标签予以区分,FreeMarker 标签以#开头。接下来讲解 FreeMarker 中 4 种常用的指令。

(1)assign指令

assign 指令用于在页面上定义一个变量,可以定义简单类型和对象类型。

  • 定义简单类型

<#assign linkman="周先生">
联系人:${linkman}

上述代码中,指令都是以“<#”开始,以“>”结束的。其中,assign 是指令名称,linkman 是定义的变量名,不是固定写法,可以任意指定。通过${变量名}的方式获取变量值。

  • 定义对象类型

<#assign info={"mobile":"13812345678",'address':'北京市昌平区'} >
电话:${info.mobile}  地址:${info.address}

上述代码中,定义对象info,对象中包含两个变量mobile和address。

(2)include指令

include指令用于文件的嵌套。例如创建文件head.ftl,文件内容如下所示。

<h1>医疗健康</h1>

创建文件test.ftl,在test.ftl文件中使用include指令引入文件head.ftl。

<html>
<#include "head.ftl"/>
<body><#assign info={"mobile":"13812345678",'address':'北京市昌平区'} >电话:${info.mobile}  地址:${info.address}
</body>
</html>

上述代码中,使用include指令引入文件head.ftl。head.ftl文件的内容会在include指令出现的位置插入。

(3)if指令

if指令用于判断,与Java中的if用法类似。

<#if x == 1>x is 1
<#else if x == 2>x is 2
<#else if x = 3>x is 4
<#else>x is one</#if>

在FreeMarker的判断中,可以使用“=”,也可以使用“==”,二者含义相同。

(4)list指令

list指令用于遍历。

<#list goodsList as goods>商品名称: ${goods.name} 价格:${goods.price}<br>
</#list>

上述代码中,goodsList 表示想要被迭代的项,可以是集合或序列;goods 表示循环变量的名称,每次迭代时,循环变量会存储当前项的值。

(1)提供静态页面模板

在 WEB-INF 目录下创建 ftl 目录,在 ftl 目录中创建模板文件 mobi le_ setmeal.ftl 和 mobile_setmeal_detail.ftl,使用 FreeMarker 技术生成套餐列表静态页面和套餐详情静态页面。

 

(2)引入 FreeMarker 的依赖

要想在Java程序中使用FreeMarker服务,需要引入FreeMarker的依赖。

 

(3)创建FreeMarker配置文件

在 resources 目录下属性文件 db.properties,指定了生成的静态页面的存放位置。

out_put_path=E:/IdeaProjects/HealthWeb/web/mobile/pages

在配置文件中如果文件内容需要换行显示,需要在换行的位置添加“\”,否则会报错;在指定静态页面存放的目录位置时,目录中不能有中文字符,否则会报错。

(4)Spring与FreeMarker 整合

在applicationContext.xml文件中添加一下配置信息

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"><!--指定模板文件所在目录--><property name="templateLoaderPath" value="/WEB-INF/ftl/" /><!--指定字符集--><property name="defaultEncoding" value="UTF-8" /></bean>

(5)生成静态页面

由于套餐新增、编辑和删除的执行过程是在 SetmealServiceImpl 类中完成的,为了方便代码调用,我们 可以在 SetmealServiceImpl 类中增加生成静态页面的代码。

 @Autowiredprivate FreeMarkerConfigurer freeMarkerConfigurer;@Value("${out_put_path}")private String outPutPath;//从属性文件中读取要生成的html对应的目录//生成当前方法所需的静态页面public void generateMobileStaticHtml(){//在生成静态页面之前需要查询数据List<Setmeal> list = setmealDao.findAll();//查询套餐列表数据generateMobileSetmealListHtml(list);//需要生成套餐列表静态页面generateMobileSetmealDetailHtml(list);//需要生成套餐详情页面}//生成套餐列表静态页面public void generateMobileSetmealListHtml(List<Setmeal> list){Map map = new HashMap();map.put("setmealList",list);//为模板提供数据,用于生成静态页面generteHtml("mobile_setmeal.ftl","m_setmeal.html",map);}//生成套餐详情静态页面(多个)public void generateMobileSetmealDetailHtml(List<Setmeal> setmealList){for (Setmeal setmeal : setmealList) {Map<String, Object> dataMap = new HashMap<String, Object>();dataMap.put("setmeal",setmealDao.findById4Detail(setmeal.getId()));generteHtml("mobile_setmeal_detail.ftl","setmeal_detail_"+setmeal.getId()+".html",dataMap);}}//通用的方法,用于生成静态页面public void generteHtml(String templateName,String htmlPageName,Map map){Configuration configuration =freeMarkerConfigurer.getConfiguration();//获得配置对象Writer out = null;try {Template template = configuration.getTemplate(templateName);//构造输出流out = new FileWriter(new File(outPutPath + "/" + htmlPageName));//输出文件template.process(map,out);out.close();} catch (Exception e) {e.printStackTrace();}}

(6)实现套餐查询方法

在 generateMobileStaticHtml( )方法中调用了 SetmealDao 接口中的 findAll( )方法查询套餐列表,调用了findById4Detail( )方法查询套餐详情,由于这 2 个方法已经在套餐列表功能和套餐详情功能中实现,所以这里不再重复讲解。

(7)完善 SetmealServiceImpl 类中的方法

当我们在管理端对套餐进行新增、编辑或删除操作时,会导致套餐内容发生改变,这时需要重新生成静态页面。在 SetmealServiceImpl 类的 add( )、edit( )和 delete( )方法中调用生成静态页面的 generateMobileStaticHtml( )方法。

(8)修改用户端访问地址

为了能够在用户端访问到生成的套餐列表静态页面,需要对 health_mobile 模块下 index.html 页面中体检预约的超链接地址进行修改,将/pages/setmeal.html 修改为/pages/m_setmeal.html。

......
<li class="type-item"><a href="/pages/m_setmeal.html" class="link-page"><div class="type-title"><h3>体检预约</h3><p>实时预约</p></div>......</a>
</li>
......

(9)测试静态页面展示

启动服务,访问http://localhost:8080/mobile/pages/index.html以执行套餐编辑操作为例,生成套餐列表静态页面与套餐详情静态页面。

在浏览器访问http://localhost:8080/mobile/pages/m_setmeal.html。

模块小结

本模块主要对用户端的体检套餐展示、体检预约和页面静态化进行了讲解。首先讲解了套餐列表和套餐详情的功能;然后讲解了体检预约的功能;最后讲解了 FreeMarker 静态页面生成技术的配置及使用,并讲解了通过静态页面展示套餐列表和套餐详情的功能。希望通过本模块的学习,可以掌握用户端套餐列表、套餐详情和体检预约的操作,熟悉 FreeMar ker 的配置及使用,并熟悉生成套餐列表和套餐详情静态页面的操作。

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

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

相关文章

【源码】最新源支付系统源码 V7版全开源 免授权 附搭建教程

最新源支付系统源码_V7版全开源_免授权_附详细搭建教程_站长亲测 YPay是专为个人站长打造的聚合免签系统&#xff0c;拥有卓越的性能和丰富的功能。它采用全新轻量化的界面UI&#xff0c;让您能更方便快捷地解决知识付费和运营赞助的难题。同时&#xff0c;它基于高性能的thin…

el-form重置后input无法输入问题

新增用户遇到的问题&#xff1a; 如果你没有为 formData 设置默认值&#xff0c;而只是将其初始化为空对象 {}&#xff0c;则在打开dialog时&#xff0c;正常输入&#xff0c; formdata会变成如下 但是&#xff0c;打开后&#xff0c;直接使用 resetFields 或直接清空表单&…

宜搭低代码开发高级认证例题1-待办列表

1、进行中待办和已完成待办界面相同 关键代码就是重要度默认为1星 2、新增自定义页面Todolist 2.1主要参数设置-新建远和API getTodoList和getDoneList代码相同 绑定代码&#xff1a;/${window.pageConfig.appType || window.g_config.appKey}/v1/form/searchFormDatas.json …

福州代理记账服务财务专业知识会计助手

福州的代理记服务可探索企业和个体工商户处理财务和会计工作。选择合适的代理记服务不仅可以节省成本&#xff0c;还可以确保财务工作专业、合规。以下是一些关于代理记服务的关键信息和财务信息&#xff0c;供您参考&#xff1a; https://www.9733.cn/news/detail/180.html …

C++ ─── vector的实现

知识点&#xff1a; ① 因为vector是模版&#xff0c;所以声明和定义都放在.h中&#xff0c;防止出现编译错误 .h不会被编译&#xff0c;在预处理中.h在.cpp中展开所以在编译时只有.cpp 而 .cpp顺序编译&#xff0c;只会进行向上查找&#xff0c;因此至少有函数的声明。 ②memc…

【数据结构与算法】堆排序算法 详解

堆排序算法 Status heapAdjust(ElemType *a, int s, int m) {ElemType t a[s];for (int j s * 2 1; j < m; j j * 2 1) {if (j < m && a[j] < a[j 1]) {j;}if (t > a[j]) {break;}a[s] a[j];s j;}a[s] t;return OK; }Status heapSort(ElemType *a…

xhs 旋转验证码剖析和协议算法实现

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#…

计算机视觉的职业规划

Hi&#xff0c;大家好。我是茶桁。 今天这节课呢&#xff0c;咱们先不着急讲原理&#xff0c;先来讲讲职业规划的话题。 如果想要直接上手企业级的 AI 项目&#xff0c;可以看看咱们的「AI 人工智能企业项目实战」。 趋势和薪资 首先&#xff0c;先来讲讲就业的趋势。其实学…

JavaWeb——MySQL:DDL

目录 3. DQL:查询 ​编辑3.3 排序查询&#xff08;order by&#xff09; &#xff08;1&#xff09;排序 &#xff08;2&#xff09;多字段排序&#xff1a; &#xff08;3&#xff09;总结&#xff1a; 3. DQL:查询 查询是使用最多、最频繁的操作&#xff0c;因为前面的…

开心汉化轻量级工单与知识库一体化管理系统源码

开心汉化发布&#xff1a;轻量级工单与知识库一体化管理系统源码 我们很高兴地宣布&#xff0c;开心汉化团队现已发布一款轻量级工单与知识库一体化管理系统的汉化源码。该系统不仅功能强大&#xff0c;而且易于部署和管理&#xff0c;适用于各类企业或个人管理大量工单数据的…

微软结束将数据中心置于海底的实验

2016 年&#xff0c;微软 宣布了一项名为"纳蒂克项目"&#xff08;Project Natick&#xff09;的实验。基本而言&#xff0c;该项目旨在了解数据中心能否在海洋水下安装和运行。经过多次较小规模的测试运行后&#xff0c;该公司于 2018 年春季在苏格兰海岸外 117 英尺…

密码学及其应用 —— Java中的安全性

1. 简介 Java是一种广泛使用的编程语言&#xff0c;特别是在企业级解决方案中&#xff0c;比如使用J2EE、JavaBeans等技术。在Web开发领域&#xff0c;Java也有其应用&#xff0c;如客户端的applet和服务器端的Servlets/JSP。 1.1 Java的特点 面向对象&#xff1a;Java是一种面…

【个人博客搭建】(26)发布后端webapi项目

1、选择启动的webapi&#xff0c;右击发布 2、选择左下角的“显示所有设置” 在上一页按钮那边是发布文件夹的目录 地址&#xff0c; 现在界面的就是配置的信息&#xff0c; 配置&#xff1a;Debug、Release 目标框架&#xff1a;我们用的net8.0&#xff0c;就是他&#xff…

FPGA学习笔记(5)——硬件调试与使用内置的集成逻辑分析仪(ILA)IP核

如果要对信号进行分析&#xff0c;可以使用外置的逻辑分析仪&#xff0c;但成本较高&#xff0c;对初学者来说没有必要&#xff0c;可以使用Xilinx Vivado内自带的逻辑分析仪IP核对信号进行分析&#xff0c;不过需要占用一定的芯片资源。 本节采用上一节配置的LED灯闪烁代码&a…

学习记录697@数据通信基础之异步通信和同步通信

最近在看计算机网络物理层部分&#xff0c;涉及到异步通信和同步通信&#xff0c;这个和通信知识相关。 异步通信和同步通信都是为了解决时钟同步问题&#xff0c;这个和编程中的同步和异步是不一样的概念。 时钟同步 我的理解是&#xff0c;发送者发送一系列信号&#xff0…

手机定位技术全解析:原理、发展与应用

1. 引言 背景介绍 最近&#xff0c;神仙姐姐刘亦菲主演的电视剧《玫瑰的故事》中的一段情节引发了广泛讨论。剧中&#xff0c;方协文&#xff08;丈夫&#xff09;对玫瑰&#xff08;妻子&#xff09;的控制欲变本加厉&#xff0c;竟然偷偷在她的手机上安装监控软件&#xff…

python笔记3

1.通过乘法多次打印&#xff0c;以及字符串相加的合体打印 xzzz yyyy print(xy) print(x*10)#与一个数为打印多少次 2.设置俩个变量&#xff0c;可以通过下面的方法来判断是否一个元素是否在另一个元素中&#xff0c;返回bool值 xzzz yyyy print(xy) print(x*10)#与一个数为打…

Android app Java层异常捕获方案

背景&#xff1a; 在Android app运行中&#xff0c;有时一些无关紧要的异常出现时希望App 不崩溃&#xff0c;能继续让用户操作&#xff0c;可以有效提升用户体验和增加业务价值。 新流程&#xff1a; 哪些场景需要Catch Crash Config配置信息&#xff1a; 支持从网络上获…

PPT录屏怎么录?PPT录屏,3种方法简单操作

在数字化时代&#xff0c;PPT已经成为我们日常工作、学习和生活中不可或缺的一部分。无论是商务报告、教学课件还是产品展示&#xff0c;PPT都能帮助我们更加生动、直观地传递信息。然而&#xff0c;有时候我们会面临PPT录屏怎么录的问题。这时&#xff0c;一个好的PPT录屏功能…

合同与合规管理:国企数字化转型之路

在全球经济一体化的背景下&#xff0c;国有企业作为国家经济的重要支撑&#xff0c;其稳健的操作和高效的管理备受瞩目。随着市场经济条件的不断演变和法规的日益严格&#xff0c;传统的手动处理合同和合规管理方式已逐步显示出局限性。采纳先进的合同管理系统和合规管理系统从…