本文目录导读:

下面给您一个完整的Java报表系统案例,从技术选型到核心代码实现,涵盖实际项目中最常用的技术方案。
需求:为企业开发一个销售报表系统,实现销售数据的可视化展示与导出功能。
技术栈:
- 后端:Spring Boot 2.x + MyBatis-Plus
- 报表引擎:JasperReports(服务端生成PDF/Excel)
- ECharts(前端可视化图表)
- 数据库:MySQL
- 工具:Maven
数据库设计
-- 销售订单表 CREATE TABLE `sales_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单号', `product_name` varchar(100) NOT NULL COMMENT '商品名称', `category` varchar(50) DEFAULT NULL COMMENT '商品分类', `quantity` int(11) NOT NULL COMMENT '销售数量', `amount` decimal(10,2) NOT NULL COMMENT '销售金额', `salesman` varchar(50) DEFAULT NULL COMMENT '销售员', `order_date` date NOT NULL COMMENT '订单日期', PRIMARY KEY (`id`), KEY `idx_order_date` (`order_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
后端核心代码
Entity实体类
@Data
@TableName("sales_order")
public class SalesOrder {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private String productName;
private String category;
private Integer quantity;
private BigDecimal amount;
private String salesman;
private Date orderDate;
}
Mapper层
@Mapper
public interface SalesOrderMapper extends BaseMapper<SalesOrder> {
// 按日期区间汇总销售额
@Select("SELECT DATE_FORMAT(order_date, '%Y-%m-%d') as date, " +
"SUM(amount) as totalAmount, SUM(quantity) as totalQuantity " +
"FROM sales_order " +
"WHERE order_date BETWEEN #{startDate} AND #{endDate} " +
"GROUP BY DATE_FORMAT(order_date, '%Y-%m-%d')")
List<Map<String, Object>> getDailySalesSummary(@Param("startDate") String startDate,
@Param("endDate") String endDate);
// 按商品分类统计
@Select("SELECT category, COUNT(*) as orderCount, " +
"SUM(amount) as totalAmount " +
"FROM sales_order " +
"WHERE order_date BETWEEN #{startDate} AND #{endDate} " +
"GROUP BY category")
List<Map<String, Object>> getCategorySummary(@Param("startDate") String startDate,
@Param("endDate") String endDate);
}
Service层
@Service
public class ReportService {
@Autowired
private SalesOrderMapper salesOrderMapper;
// 获取销售趋势数据(用于ECharts)
public Map<String, Object> getSalesTrend(String startDate, String endDate) {
List<Map<String, Object>> dailyData = salesOrderMapper.getDailySalesSummary(startDate, endDate);
List<String> dates = new ArrayList<>();
List<BigDecimal> amounts = new ArrayList<>();
for (Map<String, Object> item : dailyData) {
dates.add(item.get("date").toString());
amounts.add(new BigDecimal(item.get("totalAmount").toString()));
}
Map<String, Object> result = new HashMap<>();
result.put("dates", dates);
result.put("amounts", amounts);
return result;
}
// 获取分类占比数据
public List<Map<String, Object>> getCategoryData(String startDate, String endDate) {
return salesOrderMapper.getCategorySummary(startDate, endDate);
}
// 导出PDF报表
public byte[] exportPdfReport(String startDate, String endDate) throws Exception {
// 获取数据
List<SalesOrder> orders = salesOrderMapper.selectList(
new LambdaQueryWrapper<SalesOrder>()
.between(SalesOrder::getOrderDate, startDate, endDate)
);
// 加载JasperReports模板
InputStream inputStream = getClass().getResourceAsStream("/reports/sales_report.jrxml");
JasperReport jasperReport = JasperCompileManager.compileReport(inputStream);
// 填充数据
JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(orders);
Map<String, Object> parameters = new HashMap<>();
parameters.put("startDate", startDate);
parameters.put("endDate", endDate);
parameters.put("totalAmount", orders.stream()
.map(SalesOrder::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add));
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);
// 导出为PDF
return JasperExportManager.exportReportToPdf(jasperPrint);
}
// 导出Excel
public void exportExcel(String startDate, String endDate, HttpServletResponse response) throws Exception {
List<SalesOrder> orders = salesOrderMapper.selectList(
new LambdaQueryWrapper<SalesOrder>()
.between(SalesOrder::getOrderDate, startDate, endDate)
);
// 使用EasyExcel
EasyExcel.write(response.getOutputStream(), SalesOrder.class)
.sheet("销售报表")
.doWrite(orders);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("销售报表", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
}
}
Controller层
@RestController
@RequestMapping("/api/report")
public class ReportController {
@Autowired
private ReportService reportService;
// 获取销售趋势JSON
@GetMapping("/sales-trend")
public Result<Map<String, Object>> getSalesTrend(
@RequestParam String startDate,
@RequestParam String endDate) {
return Result.success(reportService.getSalesTrend(startDate, endDate));
}
// 获取分类数据
@GetMapping("/category")
public Result<List<Map<String, Object>>> getCategoryData(
@RequestParam String startDate,
@RequestParam String endDate) {
return Result.success(reportService.getCategoryData(startDate, endDate));
}
// 导出PDF
@GetMapping("/export/pdf")
public void exportPdf(@RequestParam String startDate,
@RequestParam String endDate,
HttpServletResponse response) throws Exception {
byte[] pdfBytes = reportService.exportPdfReport(startDate, endDate);
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=sales_report.pdf");
response.getOutputStream().write(pdfBytes);
}
// 导出Excel
@GetMapping("/export/excel")
public void exportExcel(@RequestParam String startDate,
@RequestParam String endDate,
HttpServletResponse response) throws Exception {
reportService.exportExcel(startDate, endDate, response);
}
}
前端页面(HTML + ECharts)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">销售报表系统</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<style>
.container { display: flex; flex-wrap: wrap; padding: 20px; }
.chart-container {
width: 48%;
height: 400px;
margin: 1%;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.date-input { padding: 20px; }
.btn { padding: 8px 16px; margin-right: 10px; }
</style>
</head>
<body>
<div class="date-input">
开始日期:<input type="date" id="startDate" value="2024-01-01">
结束日期:<input type="date" id="endDate" value="2024-12-31">
<button class="btn" onclick="loadData()">查询</button>
<button class="btn" onclick="exportPdf()">导出PDF</button>
<button class="btn" onclick="exportExcel()">导出Excel</button>
</div>
<div class="container">
<div id="salesTrend" class="chart-container"></div>
<div id="categoryChart" class="chart-container"></div>
</div>
<script>
// 初始化销售趋势图
var trendChart = echarts.init(document.getElementById('salesTrend'));
var categoryChart = echarts.init(document.getElementById('categoryChart'));
function loadData() {
var startDate = $('#startDate').val();
var endDate = $('#endDate').val();
// 加载销售趋势数据
$.ajax({
url: '/api/report/sales-trend',
method: 'GET',
data: { startDate: startDate, endDate: endDate },
success: function(response) {
var data = response.data;
trendChart.setOption({
title: { text: '销售趋势' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: data.dates },
yAxis: { type: 'value', name: '销售额(元)' },
series: [{
name: '销售额',
type: 'line',
smooth: true,
data: data.amounts,
areaStyle: { color: 'rgba(54, 162, 235, 0.2)' },
itemStyle: { color: '#3398DB' }
}]
});
}
});
// 加载分类占比数据
$.ajax({
url: '/api/report/category',
method: 'GET',
data: { startDate: startDate, endDate: endDate },
success: function(response) {
var data = response.data;
categoryChart.setOption({
title: { text: '商品分类销售占比' },
tooltip: { trigger: 'item', formatter: '{b}: £{c} ({d}%)' },
series: [{
name: '销售占比',
type: 'pie',
radius: '60%',
data: data.map(function(item) {
return {
name: item.category,
value: item.totalAmount
};
}),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
});
}
});
}
function exportPdf() {
var startDate = $('#startDate').val();
var endDate = $('#endDate').val();
window.location.href = '/api/report/export/pdf?startDate=' + startDate + '&endDate=' + endDate;
}
function exportExcel() {
var startDate = $('#startDate').val();
var endDate = $('#endDate').val();
window.location.href = '/api/report/export/excel?startDate=' + startDate + '&endDate=' + endDate;
}
// 页面加载时自动查询
$(document).ready(function() {
loadData();
});
</script>
</body>
</html>
JasperReports模板配置
在 src/main/resources/reports/sales_report.jrxml 中定义报表模板:
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Sales Report" pageWidth="595" pageHeight="842">
<parameter name="startDate" class="java.lang.String"/>
<parameter name="endDate" class="java.lang.String"/>
<parameter name="totalAmount" class="java.math.BigDecimal"/>
<band height="50">
<staticText>
<reportElement x="180" y="10" width="200" height="30"/>
<textElement textAlignment="Center">
<font size="16" isBold="true"/>
</textElement>
<text><![CDATA[销售报表]]></text>
</staticText>
</band>
</title>
<columnHeader>
<band height="20">
<staticText>
<reportElement x="0" y="0" width="100" height="20"/>
<text><![CDATA[订单号]]></text>
</staticText>
<staticText>
<reportElement x="100" y="0" width="100" height="20"/>
<text><![CDATA[商品名称]]></text>
</staticText>
<!-- 其他列... -->
</band>
</columnHeader>
<detail>
<band height="20">
<textField>
<reportElement x="0" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$F{orderNo}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="100" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$F{productName}]]></textFieldExpression>
</textField>
<!-- 其他列... -->
</band>
</detail>
<summary>
<band height="30">
<staticText>
<reportElement x="0" y="0" width="100" height="20"/>
<text><![CDATA[总金额:]]></text>
</staticText>
<textField>
<reportElement x="100" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$P{totalAmount}]]></textFieldExpression>
</textField>
</band>
</summary>
</jasperReport>
项目配置
pom.xml 关键依赖:
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- JasperReports -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.20.0</version>
</dependency>
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
核心功能演示效果
- 趋势图:折线图展示每日销售额变化
- 分类饼图:展示不同商品分类的销售占比
- PDF导出:服务端生成专业格式的PDF报表
- Excel导出:支持数据分析和二次加工
优化建议
| 优化方向 | 实现方案 |
|---|---|
| 大数据量 | 使用Elasticsearch做聚合分析 |
| 缓存 | Redis缓存高频查询结果 |
| 异步 | Spring @Async异步生成大报表 |
| 安全 | 加入Shiro/JWT权限控制 |
扩展功能
- 定时报表:使用Spring Scheduled定时发送邮件
- 多维度分析:按客户、区域、时间等多维度切片
- 可视化大屏:接入Grafana或自研大屏
- 智能预警:销售额异常自动告警
这个案例覆盖了报表系统从数据查询、可视化展示到文件导出的完整流程,您可以根据实际业务需求调整数据统计逻辑和展示样式。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播