统计送水数量
1修复BUG
1.1 没有计算时间段外的送水工工资
输入“起始时间”和“结束时间”,然后点击“搜索”。可以计算出在这个时间段的每个送水工的工资,还可以计算没有送水的送水工工资。
“小刘”没有在1月份为客户送水但是在其它时间段为客户送过水。下面的列表没有计算到小刘的工资。
如何解决上述BUG? 使用union对多个select语句进行联合查询:查询没有为客户送过水的送水工列表 “联合” 没有在该时间段为客户送水的送水工列表。例如:
SELECT distinct w.worker_name,w.worker_salary,w.worker_money
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
WHERE not h.send_water_time between #{startDate} and #{endDate}
union
SELECT w.worker_name,w.worker_salary,w.worker_money
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
where h.worker_id is null
Java代码也要做如下修改:
/**
* 根据条件计算某一段时间的送水工工资
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 工资列表
*/
@Override
public List<Salary> listCalcSalaryByCondition(String startDate, String endDate) {
// 条件成立:表示输入的结束时间为Null,将系统当前时间作为结束时间
if(StrUtil.isEmpty(endDate)){
long currentTime = System.currentTimeMillis();
Date dt = new Date(currentTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
endDate = sdf.format(dt);
}
// salaryList 在某个时间段已经为客户送过水的送水工信息
List<Salary> salaryList = salaryMapper.listCalcSalaryByCondition(startDate, endDate);
// sendWorkerList 在某个时间段已经为客户送过水的送水工名称列表
List<String> sendWorkerList = salaryList.stream()
.map(Salary::getWorkerName).collect(Collectors.toList());
// 没有为客户送过水的送水工信息
List<Worker> workerList = salaryMapper.queryNonSendWaterWorker(startDate,endDate);
// 将没有送水的送水工信息合并到salaryList
// 遍历workerList,将worker对象的数据注入到Salary对象中,让后添加到salaryList集合
workerList.forEach(worker->{
// 条件成立:表示没有送水的送水工在salaryList集合中不存在,将其添加到集合中
if(!sendWorkerList.contains(worker.getWorkerName())){
Salary sa = new Salary();
sa.setWorkerName(worker.getWorkerName());
sa.setWorkerSalary(worker.getWorkerSalary());
sa.setWorkerMoney(worker.getWorkerMoney());
// 没有送水的送水工默认送水数量为0
sa.setSendWaterCount(0);
// 没有送水的送水工默认实发工资为基本工资
sa.setFinalSalary(Double.valueOf(worker.getWorkerSalary()));
salaryList.add(sa);
}
});
// salaryList集合可能有重复的数据,去掉重复的数据
Collections.sort(salaryList,(o1,o2)->{
if(o1.getFinalSalary() > o2.getFinalSalary()){
return -1;
}else if(o1.getFinalSalary() == o2.getFinalSalary()) {
return 0;
} else {
return 1;
}
});
return salaryList;
}
1.2 没有对送水工工资进行排序
如何解决:按照送水工的工资进行比较,降序排序
Collections.sort(salaryList,(o1,o2)->{
if(o1.getFinalSalary() > o2.getFinalSalary()){
return -1;
}else if(o1.getFinalSalary() == o2.getFinalSalary()) {
return 0;
} else {
return 1;
}
});
2 统计送水工送水数量
2.1 需求分析
原始需求:统计每个送水工为那些客户总共送了多少桶水
送水工名称 | 客户列表 | 送水数量 |
小刘 | 老刘,老张 | 761 |
小李 | 李老,老陈 | 66 |
小张 | 老张 | 60 |
小唐 | - | 0 |
需求分析:
-
首先确定表:
tb_worker
,tb_history
,tb_customer
-
然后确定列:
tb_worker
表的worker_name
,tb_customer
表的cust_name
,tb_history
表的send_water_count
-
确定哪些列是原始列,哪些列是需要计算的:
tb_worker
表的worker_name
列是原始列。使用group_concat
函数对tb_customer
表的cust_name
列需要对每个客户进行拼接,然后汇总。使用聚合函数sum
对tb_history
表的send_water_count
列进行累加求和。 -
确定要分组的列:对
tb_worker
表的worker_name
列进行分组 -
对“送水数量”进行降序排序
SELECT w.worker_name ,
ifnull(GROUP_CONCAT(distinct c.cust_name),'-') as cust_details,
ifnull(sum(h.send_water_count),0) as send_water_count
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
left join tb_customer c on h.cust_id = c.cid
GROUP BY w.worker_name
ORDER BY send_water_count desc
GROUP_CONCAT :
1、功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
2、语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )
说明:通过使用distinct可以排除重复值;如果希望对结果中的值进行排序,可以使用order by子句;separator是一个字符串值,缺省为一个逗号。
https://blog.csdn.net/mary19920410/article/details/76545053
2.2 操作步骤
2.2.1 编写实体类WaterDetails
这个实体类 也是没有表,是我们根据需要自己封装的一个实体类
package com.shouyi.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* TODO: 详细送水信息实体类
* @author caojie
* @version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WaterDetails {
/**
* 送水工名称
*/
private String workerName;
/**
* 客户详细信息
*/
private String custDetails;
/**
* 送水数量
*/
private Integer sendWaterCount;
}
2.2.2 编写WaterDetailsMapper接口
package com.shouyi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shouyi.entities.WaterDetails;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* TODO 送水详细信息映射器
* @author caojie
* @version 1.0
*/
@Repository
public interface WaterDetailsMapper extends BaseMapper<WaterDetails> {
/**
* 查询每个送水工送水的详细信息
* @return 送水详细信息列表
*/
List<WaterDetails> querySendWaterDetails();
}
2.2.3 编写Mapper对应的映射文件 WaterDetailsMapper.xml
<?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.lxyk.mapper.WaterDetailsMapper">
<!-- 查询每个送水工的送水详细信息列表-->
<select id="querySendWaterDetails" resultType="WaterDetails">
SELECT w.worker_name ,
ifnull(GROUP_CONCAT(DISTINCT c.cust_name),'-') as cust_details,
ifnull(sum(h.send_water_count),0) send_water_count
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
left join tb_customer c on c.cid = h.cust_id
GROUP BY w.worker_name
ORDER BY send_water_count desc
</select>
</mapper>
2.2.4 编写WaterDetailsService接口
package com.shouyi.service;
import com.shouyi.entities.WaterDetails;
import java.util.List;
/**
* TODO: 送水详细信息业务逻辑接口
* @author caojie
* @version 1.0
*/
public interface WaterDetailsService {
/**
* 查询每个送水工送水的详细信息
* @return 送水详细信息列表
*/
List<WaterDetails> querySendWaterDetails();
}
2.2.5 编写接口实现类
package com.shouyi.service.impl;
import com.shouyi.entities.WaterDetails;
import com.shouyi.mapper.WaterDetailsMapper;
import com.shouyi.service.WaterDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* TODO
* @author caojie
* @version 1.0
*/
@Service
public class WaterDetailsServiceImpl implements WaterDetailsService {
@Autowired
private WaterDetailsMapper waterDetailsMapper;
/**
* 查询每个送水工送水的详细信息
* @return 送水详细信息列表
*/
@Override
public List<WaterDetails> querySendWaterDetails() {
return waterDetailsMapper.querySendWaterDetails();
}
}
2.2.6 编写Controller控制器
package com.shouyi.controller;
import com.shouyi.entities.WaterDetails;
import com.shouyi.service.WaterDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* TODO: 统计送水信息的控制器
* @author caojie
* @version 1.0
*/
@RequestMapping("/stat")
@Controller
@Slf4j
public class WaterDetailsController {
@Autowired
private WaterDetailsService waterDetailsService;
/**
* 点击“统计送水数量”,查询每个客户送水信息,然后将数据渲染到前端页面,最后返回"统计送水数量列表"页面
* @param model
* @return "统计送水数量列表"页面
*/
@RequestMapping("/statWaterDetails")
public String statWaterDetails(Model model) {
// 送水详细信息列表
List<WaterDetails> waterList = waterDetailsService.querySendWaterDetails();
model.addAttribute("waterList",waterList);
return "waterDetailsList";
}
}
2.2.7 编写前端页面
新建前端页面waterDetailsList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title> 冯宝宝送水后台管理系统</title>
<!--Bootstrap固定框架-->
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}">
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}">
<!--图标库-->
<link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'>
<!--核心样式-->
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<div id="viewport">
<!-- Sidebar
客户列表页面使用th:replace属性替换成主菜单的侧边栏,让代码能够复用
th:replace="waterMainMenu::sidebar"
waterMainMenu表示主菜单页面的文件名称
sidebar表示主菜单页面的片段名称
-->
<div id="sidebar" th:replace="waterMainMenu::sidebar">
</div>
<!-- Content -->
<div id="content">
<!--
th:replace="waterMainMenu::navbar"表示将nav标签里面所有的内容替换为主页面的navbar片段
-->
<nav class="navbar navbar-default" th:replace="waterMainMenu::navbar">
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-hover table-striped">
<thead>
<tr>
<td>送水工名称</td>
<td>客户列表</td>
<td>送水数量</td>
</tr>
</thead>
<tbody>
<tr th:each="water : ${waterList}">
<td th:text="${water.workerName}"></td>
<td th:text="${water.custDetails}"></td>
<td th:text="${water.sendWaterCount}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
2.2.8 修改主页面
<li>
<a th:href="@{/stat/statWaterDetails}">
<i class="zmdi zmdi-widgets"></i> 统计送水数量
</a>
</li>
3调整工资
进入“送水工列表”页面,点击“+”将工人工资增加100。然后弹出提示框“调整工资成功”。完成该功能前端需要使用jQuery
框架。
3.1 导入jQuery框架
往哪里导?哪个界面需要往哪个界面里导,谁需要?“送水工列表”页面,放在哪?<head>标签里
<!--引入jQuery库-->
<script th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
哪为什么能导入成功?因为我们在项目之初就导入了依赖
3.2 编写送水工页面
“送水工底薪”旁边添加+
和-
<!-- <td th:text="${worker.workerSalary}"></td>-->
<td>
<span id="add" th:class="${worker.wid}" >+</span>
<span id="salary" th:text="${worker.workerSalary}"></span>
<span id="sub" th:class="${worker.wid}">-</span>
</td>
3.3 为span标签设置样式
#add,#sub{
font-weight: bold;
font-size:20px;
color:red;
}
样式随便找个地方放就可以了,如:放在当前页面(“送水工列表”)的<head>标签中:<style></style>里
3.4 编写调整工资的前端代码
+的写法
<script>
$(function(){
// 为“+”绑定“单击事件”
$("span[id=add]").click(function(){
// 获取送水工ID
let workerid =$(this).attr("class");
// 选择+下一个元素,获取送水工工资
let workerSalary = $(this).next().text();
// 计算调整之后的新工资
workerSalary = parseInt(workerSalary);
workerSalary+=100;
// 将新工资设置到页面
$(this).next().text(workerSalary);
// 使用ajax技术向后端发起调整工资的异步请求
$.ajax({
// 请求的URL
url:'/worker/adjustSalary',
// 提交给后端服务器的数据
data:{
wid:workerid,
workerSalary:workerSalary
},
// 请求的方式为“POST”
method:"POST",
// 请求成功后的回调函数
success:function(data,status) {
if(data =="OK") {
alert("调整工资成功");
} else {
alert("调整工资失败");
}
}
})
})
})
</script>
-号的写法
<script>
$("span[id=sub]").click(function(){
// 获取送水工的工资,然后-100,将调整后的工资重新设置到table表格中 this是对象的本身
let workerId = $(this).attr("class");
// $(this).prev() 获取-前面的一个元素
// $(this).prev().text()获取-前面一个元素的文本(送水工工资)
let workerSalary = $(this).prev().text();
// 调整工资(-100)
workerSalary = parseInt(workerSalary);
workerSalary = workerSalary - 100;
// 把调整之后的工资重新设置到表格的span标签中
$(this).prev().text(workerSalary);
// 前端jQuery框架使用异步ajax技术向后端发起请求,将调整之后的工资更新到后端
$.ajax({
// 前端发起的请求路径
url:'/worker/adjustSalary',
// 前端封装的参数
data:{
wid: workerId,
workerSalary: workerSalary
},
// 请求方式
method:'POST',
// 请求成功的回调函数
success:function(data,status){
if(data == "OK") {
alert("调整工资成功");
} else {
alert("调整工资失败");
}
}
})
})
</script>
let workerid =$(this).attr("class");放进去的是这个class<div class="col-md-12">
3.5 编写WorkerMapper接口
在WorkerMapper
接口新增加一个方法
/**
* 调整送水工工资,在原有基础上增加100
* @param wid 送水工ID
* @param workerSalary 送水工工资
* @return 受影响行数。大于0调整工资成功,否则调整工资失败
*/
int adjustSalary(@Param("wid")Integer wid,@Param("workerSalary")Integer workerSalary);
3.6 编写WorkerMapper映射文件
创建WorkerMapper.xml
映射文件
<?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.shouyi.mapper.WorkerMapper">
<update id="adjustSalary" parameterType="java.lang.Integer">
update tb_worker
set worker_salary = #{workerSalary}
where wid = #{wid}
</update>
</mapper>
3.7 编写Service接口
在WorkerService
接口新增加一个调整工资的方法
/**
* 调整工资
* @param wid 送水工ID
* @param workerSalary 送水工工资
* @return 受影响行数。大于0调整工资成功,否则调整工资失败
*/
int adjustSalary(Integer wid,Integer workerSalary);
3.8编写接口实现类
在WorkerServiceImpl
接口实现类覆写WorkerService
接口的adjustSalary
方法
/**
* 调整工资
*
* @param wid 送水工ID
* @param workerSalary 送水工工资
* @return 受影响行数。大于0调整工资成功,否则调整工资失败
*/
@Override
public int adjustSalary(Integer wid, Integer workerSalary) {
return workerMapper.adjustSalary(wid,workerSalary);
}
3.9 编写Controller控制器
在WorkerController
新增一个方法,专门用来处理"调增工资"的请求
/**
处理"调增工资"的请求
@ResponseBody表示将Java对象转换为json格式的数据渲染到前端页面
*/
@RequestMapping(value="/adjustSalary",method = RequestMethod.POST)
@ResponseBody
public String adjustWorkerSalary(Integer wid,Integer workerSalary) {
log.info("adjustWorkerSalary wid = "+wid);
log.info("adjustWorkerSalary workerSalary = "+workerSalary);
int rows = workerService.adjustSalary(wid, workerSalary);
log.info("adjustWorkerSalary rows = "+rows );
if (rows > 0) {
return "OK";
} else {
return "Fail";
}
}
@ResponseBody表示将Java对象转换为json格式的数据渲染到前端页面