一、报表
- 报表的概念:
连接多张数据表,通过一系列的运算得到的结果.
报表就是用表格、图表等格式来动态显示数据,可以用公式表示为:“报表 = 多样的格式 + 动态的数据” - 报表的实现方式:
表格:详细数据
图表: 直观 - 报表常识:
生成的数据报表,不具有增加、删除、修改数据的功能。只能够查询数据。
只返回前端需要的数据,以此来提高性能。 - 在此处需要连接的数据库表有:
采购订单明细、采购订单、供货商、员工表、产品表、产品类型表 - 达到的效果
页面查询效果;
图像页面效果:
二、使用EasyUI(datagrid-groupview) 控件
先在EasyUi官网扩展处找到数据网格分组视图(DataGrid GroupView)
- 下载相应的控件
datagrid-groupview.js文件 - 研究插件源码
jsp页面:
<body>
<table id="tt"></table>
</body>
使用js代码创建数据表格
<script>
$(function(){
$('#tt').datagrid({
title:'DataGrid - GroupView',
width:500,
height:250,
rownumbers:true,
remoteSort:false, //是否支持远程排序
nowrap:false,
fitColumns:true,
url:'datagrid_data.json', //访问数据的路径
columns:[[
{field:'productid',title:'Product ID',width:100,sortable:true},
{field:'listprice',title:'List Price',width:80,align:'right',sortable:true},
{field:'unitcost',title:'Unit Cost',width:80,align:'right',sortable:true},
{field:'attr1',title:'Attribute',width:150,sortable:true},
{field:'status',title:'Status',width:60,align:'center'}
]],
groupField:'productid', //根据哪一个字段分组
view: groupview, //分组视图必需要加这个
//value:分组的值 rows:这一组的所有值
groupFormatter:function(value, rows){ //分组位置的显示
return value + ' - ' + rows.length + ' Item(s)';
}
});
});
</script>
路径datagrid_data.json显示写死,以后会根据自己路径访问
三、创建项目的报表
-
注意:报表是多张表连接起来,然后运算出一些结果
-
专门为报表中的字段新建一个domain,来装报表中需要的数据
vo:value object值对象。用于业务之间的数据传递,和po一样,仅仅包含数据而已。new出来的抽象对象。
po:persistent object持久对象。通常对应数据库,本身还有部分业务逻辑处理,可看成是与数据库中的表相隐射的java对象。po中应该不包含任何对数据库的操作。 -
准备一个报表的vo类PurchasebillitemVo.java。报表展示中需要的字段。
准备:- 将 表单明细Purchasebillitem对象装换为报表的PurchasebillitemVo对象,通过构造方法。
- springmvc处理时间的格式:使用注解。在get方法上使用 @JsonFormat(pattern = “yyyy-MM-dd”,timezone = “GMT+8”)
- 解决分组字段写死问题(供应商、采购员和月份),定义一个分组字段。就是在Purchasebillitem.js中的分组字段。js中的分组字段就决定了页面以什么字段展示数据。
/*
* 报表字段。所有数据是要返回给前台的
* */
public class PurchasebillitemVo {
//编号
private Long id;
//供应商
private String supplier;
//采购员
private String buyer;
//产品
private String product;
//产品类型
private String productType;
//交易时间
private Date vdate;
//采购数量
private BigDecimal num;
//价格
private BigDecimal price;
//小计=价格*数量
private BigDecimal amount;
//状态 0表示待审 1表示以审 -1表示作废
private Integer status;//默认就是待审状态
//分组字段。定义一个分组字段,好在PurchasebillitemVo.js中能灵活的拿到分组字段,解决写死问题
private String groupField;
public PurchasebillitemVo(){}
//--------------------------------------------------------------------------
//提供一个有参构造,当new PurchasebillitemVo的时候就直接将将每一个Purchasebillitem对象变成PurchasebillitemVo对象
public PurchasebillitemVo (Purchasebillitem item, PurchasebillitemQuery query){
this.id=item.getId();
//PurchasebillitemVo对象=Purchasebillitem对象的关联表的字段BillId的关联的供货商字段supplier的供货商类Supplier名字
this.supplier=item.getBillId().getSupplier().getName();
//查找方法与上面的一样,注意不同表之间的关联字段,分部拿到就可以相应的值
this.buyer=item.getBillId().getBuyer().getUsername();
this.product=item.getProduct().getName();
this.productType=item.getProduct().getTypes().getName();
this.vdate=item.getBillId().getVdate();
this.num=item.getNum();
this.price=item.getPrice();
this.amount=item.getAmount();
this.status=item.getBillId().getStatus();
System.out.println(status);
//----------------------------------------------------------------------
//拿到分组字段。就是PurchasebillitemVo.js中的以什么字段分组
//只能一个一个的获取,不符合业务
//如果是供应商分组
//this.groupField = this.supplierName;
//如果是采购员分组
//this.groupField = this.buyerName;
//如果是月份
//Calendar cal = Calendar.getInstance();
//cal.setTime(vdate);
//this.groupField = (cal.get(Calendar.MONTH)+1) + "月";
//---------------PurchasebillitemQuery就是前台传过来的查询对象-----------------------------
//通过witch语句完成判断分组字段。从PurchasebillitemQuery中拿到分组字段
switch (query.getGroupType()){//变量分别可能为1、2、3,然后对应下面的值
case 1:
this.groupField = this.supplier;
break;
case 2:
this.groupField = this.buyer;
break;
case 3:
//从交易日期中拿到月份
Calendar cal = Calendar.getInstance();
cal.setTime(vdate);
this.groupField = (cal.get(Calendar.MONTH)+1) + "月";
break;
default:
//定义默认为供应商
this.groupField = this.supplier;
}
}
//-------------------------------------------------------------------
//springmvc处理时间格式。响应给前台
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
public Date getVdate() {
return vdate;
}
public void setVdate(Date vdate) {
this.vdate = vdate;
}
//省略其他字段的get、set方法
- query层。(开始时间、结束时间、状态、分组字段)
其中分组字段groupType就是Purchasebillitem.jsp中对应的分组字段的name值
public class PurchasebillitemQuery extends BaseQuery {
//开始时间
private Date beginDate;
//结束时间
private Date endDate;
//状态
private Integer status;
//-----------分组字段------------------------------
//定义一个分组的字段,已解决分组字段写死问题,从前台传过来。1.供应商 2.采购员 3.月份
private Integer groupType=1;//默认为供应商
//----------------------------------------
//直接返回Specification这个对象
/*
*重点:大于等于开始时间,小于结束时间加一天
* */
@Override
public Specification createSpec() {
//根据条件把数据返回即可
//结束时间加一天
Date tempDate=null;
if(endDate!=null){
//DateUtils:lang3包的方法
tempDate= DateUtils.addDays(endDate,1 );
}
//----------------------------------------------------
//gt:大于 ge:大于等于
//lt:小于 le:小于等于 eq:等于
Specification<Purchasebillitem> spec = Specifications.<Purchasebillitem>and()
.ge(beginDate!=null,"billId.vdate",beginDate)//Purchasebillitem上没有vdate字段,billId这个属性才有
.lt(endDate!=null,"billId.vdate",tempDate)
.eq(status!=null,"billId.status",status)
.build();
return spec;
}
//----------------------------------------------------
public Date getBeginDate() {
return beginDate;
}
//在SpringMVC中,如果要接收日期参数,一般都要加上注解
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setBeginDate(Date beginDate) {
this.beginDate = beginDate;
}
//在SpringMVC中,如果要接收日期参数,一般都要加上注解
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Date getEndDate() {
return endDate;
}
//省略get、set方法
- repository层的父类层写了个根据查询对象查询数据的方法
//根据Query拿到对应的有数据(不分所页)
List<T> findByQuery(BaseQuery baseQuery);
- service层的接口写一个根据查询条件去数据库查询的方法
//根据查询对象获取到订单报表中的所有明细.泛型就确认类型是PurchasebillitemVo
List<PurchasebillitemVo> findItems(PurchasebillitemQuery query);
- service层的实现类。主要就是根据查询对象将数据查出来,然后将Purchasebillitem数据通过上面的构造函数转换为报表PurchasebillitemVo对象
@Service
public class PurchasebillitemServiceImpl extends BaseServiceImpl<Purchasebillitem,Long> implements IPurchasebillitemService {
@Autowired
private PurchasebillitemRepository purchasebillitemRepository;
//将查询出来的订单明细Purchasebillitem对象转换为报表PurchasebillitemVo对象。
@Override
public List<PurchasebillitemVo> findItems(PurchasebillitemQuery query) {
//1.准备一个空的PurchasebillitemVO集合容器
List<PurchasebillitemVo> vos=new ArrayList<>();
//2.根据查询条件获得所有的订单明细
List<Purchasebillitem> byQuery = purchasebillitemRepository.findByQuery(query);
//3.遍历查询出来的所有数据(将每一个Purchasebillitem对象变成PurchasebillitemVo对象)
for (Purchasebillitem purchasebillitem : byQuery) {
//4.在PurchasebillitemVo的domain中创建了个构造函数(在new的时候传参过去的purchasebillitem会返回一个purchasebillitemVo)
PurchasebillitemVo purchasebillitemVo = new PurchasebillitemVo(purchasebillitem,query);
//5.将得到的purchasebillitemVo对象装进集合中去
vos.add(purchasebillitemVo);
}
//6.将purchasebillitemVo集合返回
return vos;
}
}
- controller层
@RequestMapping("/findItems")
public List<PurchasebillitemVo> findItems(PurchasebillitemQuery query) {
return purchasebillitemService.findItems(query);
}
jsp层:
js:拷贝插件中的代码修改
$('#purchaseBillItemGrid').datagrid({
fit:true,
rownumbers:true,
remoteSort:false, //是否支持远程排序
nowrap:false,
fitColumns:true,
singleSelect:true,
toolbar:"#gridToolBar",
url:'/purchasebillitem/findItems', //访问数据的路径
columns:[[
{field:'id',title:'编号',width:20,sortable:true},
{field:'supplier',title:'供应商',width:80,align:'right',sortable:true},
{field:'buyer',title:'采购员',width:80,align:'right',sortable:true},
{field:'product',title:'产品',width:40,sortable:true},
{field:'productType',title:'产品类型',width:60,align:'center'},
{field:'vdate',title:'交易时间',width:60,align:'center'},
{field:'price',title:'价格',width:60,align:'center'},
{field:'num',title:'数量',width:60,align:'center'},
{field:'amount',title:'小计',width:60,align:'center'},
{field:'status',title:'状态',width:60,align:'center',formatter:formatStatus}
]],
groupField:'groupField', //根据哪一个字段分组
view: groupview, //分组视图必需要加这个
//value:分组的值 rows:这一组的所有值
groupFormatter:function(value, rows){ //分组位置的显示
//总数量
var totalNum=0;
//总金额
var totalAmount=0;
//遍历拿到的组的所有值
for(let e of rows){
//数量相加
totalNum+=e.num;
//金额相加
totalAmount+=e.amount;
}
return value + ' - ' + `${rows.length}条数据
<span style="color: #00ee00">共${totalNum}个商品</span>
<span style="color: #ee4b95">总金额:${totalAmount}</span>
`;
}
});
- 总结图:
四、图像页面效果报表
使用3D饼图
- 使用flash技术和H5技术可以做出来
- 使用插件:EChars 、HighChart
- 项目中引入HighChart
- 在purchasebillitem.jsp中
引入highcharts中的要使用的js文件
在这里插入图片描述
- 找到要使用的3D图的原JS代码并拷贝过来加以改动
- 在purchasebillitem.jsp中新增一个3D图按钮
<a href="javascript:;" data-method="chart3d" class="easyui-linkbutton" iconCls="icon-search">3D图</a>
- 在purchasebillitem.js中给3D按钮的data-method="chart3d"注册一个事件,点击按钮就能打开3D图形报表。
chart3d(){
//弹出相应的diagle
chartDialog.dialog("center").dialog("open");
//把表单中的参数传过去。
var params = searchForm.serializeObject();
//发送ajax请求到后台,并将form表单中的额外参数(时间、状态)也一并提交到后台处理
$.post("/purchasebillitem/findCharts",params,function (result) {
//数据得到后进行展示
Highcharts.chart('chartDialog', {
chart: {
type: 'pie',
options3d: {
enabled: true,
alpha: 45,
beta: 0
}
},
title: {
text: '采购订单图形报表'
},
// 鼠标移上去的文字
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer', //指针样式
depth: 30,
dataLabels: {
enabled: true,
format: '{point.name}'
}
}
},
//饼中展示的数据(从数据库中查询出来的)
series: [{
type: 'pie',
name: '移上来',
// 数据。map集合形式。通过上面的ajax请求将map集合的数据从后台传到前台展示
data: result
}]
});
})
},
- 获取3D饼图要展示的数据,从数据库中查询出来。
在js中3D图需要的数据类型是map集合形式。
IPurchasebillitemService层:
//获取3D饼图形中需要的数据(在purchasebillitem.jsp中3D饼图展示的数据时map集合形式)
List<Map> findCharts(PurchasebillitemQuery query);
service实现类
- purchasebillitemQuery层;接收前台传过来的查询对象参数
/*
* 报表的高级查询
* */
public class PurchasebillitemQuery extends BaseQuery {
//开始时间
private Date beginDate;
//结束时间
private Date endDate;
//状态
private Integer status;
//定义一个分组的字段,已解决分组字段写死问题,从前台传过来。1.供应商 2.采购员 3.月份
private Integer groupType=1;//默认为供应商
//直接返回Specification这个对象
/*
*重点:大于等于开始时间,小于结束时间加一天
* */
@Override
public Specification createSpec() {
//根据条件把数据返回即可
//结束时间加一天
Date tempDate=null;
if(endDate!=null){
//DateUtils:lang3包的方法
tempDate= DateUtils.addDays(endDate,1 );
}
//gt:大于 ge:大于等于
//lt:小于 le:小于等于 eq:等于
Specification<Purchasebillitem> spec = Specifications.<Purchasebillitem>and()
//Purchasebillitem上没有vdate字段,billId这个属性才有
.ge(beginDate!=null,"billId.vdate",beginDate)
.lt(endDate!=null,"billId.vdate",tempDate)
.eq(status!=null,"billId.status",status)
.build();
return spec;
}
/*
String类型的字符串拼接:三种方式的区别
*String:不适合用于字符串拼接(每次都创建一个新的,性能比较低)
* StringBuffer:拼接字符串性能高,线程安全
* StringBuilder:拼接字符串性能最高,线程不安全
* 只返回 JPQL : where status = ? and date>= beginDate and ...
* */
//准备一个parmas集合用来接收JS中的ajax请求传过来的额外参数
private List parmas=new ArrayList();
//----------拿到前台传来的查询条件(开始时间、结束时间、状态)用于拼接jpql-------
//提供一个拼接jpql的方法
public String createWhereJPQL(){
StringBuilder jpql = new StringBuilder();
if(beginDate!=null){
//大于等于开始时间
jpql.append(" and o.billId.vdate >=? ");
parmas.add(beginDate);
}
if(endDate!=null){
//小于结束时间加上1天
Date tempDate = DateUtils.addDays(endDate,1);
jpql.append(" and o.billId.vdate<? ");
parmas.add(tempDate);
}
if(status!=null){
//状态
jpql.append(" and o.billId.status=? ");
parmas.add(status);
}
//重点:拼接JPQL语句的时候将第一个and替换成where
return jpql.toString().replaceFirst("and","where");
}
//--------------------拿到分组对象值,用于拼接jpql---------------------------
//拿到分组的值。前台会传查询的query对象(供应商、采购员、月份)
public String getGroupName(){
if(groupType==1){
return "o.billId.supplier.name";
}else if(groupType==2){
return "o.billId.buyer.username";
}else if(groupType==3){
//获取到日期的月份
return "MONTH(o.billId.vdate)";
}else{
return "o.billId.supplier.name";
}
}
//在SpringMVC中,如果要接收日期参数,一般都要加上注解
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setBeginDate(Date beginDate) {
this.beginDate = beginDate;
}
//在SpringMVC中,如果要接收日期参数,一般都要加上注解
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
- controller层
//3D图像报表的ajax请求数据路径
@RequestMapping("/findCharts")
@ResponseBody//返回json数据
public List<Map> findCharts(PurchasebillitemQuery query) {
return purchasebillitemService.findCharts(query);
}
- 图解