跳到主要内容
Spring Boot 数据可视化与图表集成实战 | 极客日志
Java 大前端 java
Spring Boot 数据可视化与图表集成实战 Spring Boot 结合 ECharts 实现数据可视化,通过 Thymeleaf 模板引擎将后端 Java 数据注入前端 JavaScript 脚本。文章详细演示了从依赖配置、实体类设计、服务层逻辑到前端图表渲染的全流程。重点解决了如何在 HTML 脚本块中安全传递列表数据的问题,实现了产品销量等数据的直观展示。该方案适用于后台管理系统中的销售报表、用户分析及运营监控等场景,帮助开发者快速构建交互式数据大屏。
Spring Boot 数据可视化与图表集成实战
在 Java 开发中,单纯的数据列表往往难以直观反映业务趋势。将后端数据通过图表形式展示,不仅能提升可读性,还能辅助快速决策。本文将带你从零开始,在 Spring Boot 项目中集成 ECharts,实现产品销量等数据的可视化展示。
为什么需要数据可视化
数据可视化的核心在于将抽象数据转化为直观的图形。常见的工具包括 ECharts、Highcharts 和 D3.js 等。对于 Spring Boot 项目,ECharts 因其文档完善、功能丰富且开源免费,成为首选方案之一。
项目搭建与依赖配置
首先,我们需要创建一个标准的 Spring Boot Web 项目。在 pom.xml 中引入 Web 和 Thymeleaf 依赖,Thymeleaf 能方便地在服务端渲染 HTML 并嵌入动态数据。
<dependencies >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-thymeleaf</artifactId >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
< > spring-boot-starter-test
test
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
artifactId
</artifactId >
<scope >
</scope >
</dependency >
</dependencies >
接着配置 application.properties,关闭缓存以便调试时能即时看到页面变化:
server.port=8080
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.suffix=.html
spring.thymeleaf.prefix=classpath:/templates/
后端数据层实现 为了演示效果,我们构建一个简单的产品管理模块。实体类定义需规范,注意字段类型和 Getter/Setter 的完整性。
public class Product {
private Long id;
private String productId;
private String productName;
private double price;
private int sales;
public Product () {}
public Product (Long id, String productId, String productName, double price, int sales) {
this .id = id;
this .productId = productId;
this .productName = productName;
this .price = price;
this .sales = sales;
}
public Long getId () { return id; }
public void setId (Long id) { this .id = id; }
public String getProductId () { return productId; }
public void setProductId (String productId) { this .productId = productId; }
public String getProductName () { return productName; }
public void setProductName (String productName) { this .productName = productName; }
public double getPrice () { return price; }
public void setPrice (double price) { this .price = price; }
public int getSales () { return sales; }
public void setSales (int sales) { this .sales = sales; }
@Override
public String toString () {
return "Product{" +
"id=" + id +
", productId='" + productId + '\'' +
", productName='" + productName + '\'' +
", price=" + price +
", sales=" + sales +
'}' ;
}
}
使用内存列表模拟数据库操作,便于快速验证逻辑。Repository 层负责数据的增删改查:
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Repository
public class ProductRepository {
private List<Product> products = new ArrayList <>();
public ProductRepository () {
products.add(new Product (1L , "P001" , "手机" , 1000.0 , 100 ));
products.add(new Product (2L , "P002" , "电脑" , 5000.0 , 50 ));
products.add(new Product (3L , "P003" , "电视" , 3000.0 , 80 ));
products.add(new Product (4L , "P004" , "手表" , 500.0 , 200 ));
products.add(new Product (5L , "P005" , "耳机" , 300.0 , 150 ));
}
public List<Product> getAllProducts () {
return products;
}
public Product getProductById (Long id) {
return products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null );
}
public void addProduct (Product product) {
product.setId((long ) (products.size() + 1 ));
products.add(product);
}
public void updateProduct (Product product) {
Product existing = getProductById(product.getId());
if (existing != null ) {
existing.setProductId(product.getProductId());
existing.setProductName(product.getProductName());
existing.setPrice(product.getPrice());
existing.setSales(product.getSales());
}
}
public void deleteProduct (Long id) {
products.removeIf(p -> p.getId().equals(id));
}
}
Service 层简单透传 Repository 方法,Controller 层负责路由映射和模型数据传递:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/")
public String getAllProducts (Model model) {
List<Product> products = productService.getAllProducts();
model.addAttribute("products" , products);
return "product-list" ;
}
@GetMapping("/{id}")
public String getProductById (@PathVariable Long id, Model model) {
Product product = productService.getProductById(id);
model.addAttribute("product" , product);
return "product-detail" ;
}
@GetMapping("/add")
public String addProductForm (Model model) {
model.addAttribute("product" , new Product ());
return "product-form" ;
}
@PostMapping("/add")
public String addProduct (@ModelAttribute Product product) {
productService.addProduct(product);
return "redirect:/api/products/" ;
}
@GetMapping("/edit/{id}")
public String editProductForm (@PathVariable Long id, Model model) {
Product product = productService.getProductById(id);
model.addAttribute("product" , product);
return "product-form" ;
}
@PostMapping("/edit/{id}")
public String editProduct (@PathVariable Long id, @ModelAttribute Product product) {
product.setId(id);
productService.updateProduct(product);
return "redirect:/api/products/" ;
}
@GetMapping("/delete/{id}")
public String deleteProduct (@PathVariable Long id) {
productService.deleteProduct(id);
return "redirect:/api/products/" ;
}
}
前端页面与 ECharts 集成 这是最关键的一步。我们需要在 Thymeleaf 模板中引入 ECharts 库,并将后端传来的数据序列化为 JavaScript 数组。
<!DOCTYPE html >
<html lang ="zh-CN" xmlns:th ="http://www.thymeleaf.org" >
<head >
<meta charset ="UTF-8" >
<title > 产品列表</title >
<script src ="https://cdn.jsdelivr.net/npm/[email protected] /dist/echarts.min.js" > </script >
</head >
<body >
<h1 > 产品列表</h1 >
<a href ="/api/products/add" > 添加产品</a >
<table border ="1" >
<thead >
<tr >
<th > ID</th >
<th > 产品 ID</th >
<th > 产品名称</th >
<th > 价格</th >
<th > 销量</th >
<th > 操作</th >
</tr >
</thead >
<tbody >
<tr th:each ="product : ${products}" >
<td th:text ="${product.id}" > </td >
<td th:text ="${product.productId}" > </td >
<td th:text ="${product.productName}" > </td >
<td th:text ="${product.price}" > </td >
<td th:text ="${product.sales}" > </td >
<td >
<a th:href ="@{/api/products/edit/{id}(id=${product.id})}" > 编辑</a >
<a th:href ="@{/api/products/delete/{id}(id=${product.id})}" > 删除</a >
</td >
</tr >
</tbody >
</table >
<h2 > 产品销量图表</h2 >
<div id ="salesChart" style ="width: 800px;height: 400px;" > </div >
<script >
var chartDom = document .getElementById ('salesChart' );
var myChart = echarts.init (chartDom);
var option;
var productNames = [];
var productSales = [];
<th:block th:each ="product : ${products}" >
productNames.push('<span th:text ="${product.productName}" > </span > ');
productSales.push(<span th:text ="${product.sales}" > </span > );
</th:block >
option = {
title : {
text : '产品销量图表' ,
left : 'center'
},
tooltip : {
trigger : 'item'
},
legend : {
orient : 'vertical' ,
right : 10 ,
top : 'center'
},
series : [{
name : '销量' ,
type : 'pie' ,
radius : [ , ],
: ,
: {
: ,
: ,
:
},
: {
: ,
:
},
: {
: {
: ,
: ,
:
}
},
: {
:
},
: [
]
}]
};
option && myChart. (option);
</script >
</body >
</html >
启动应用后访问 http://localhost:8080/api/products/,即可看到包含产品列表和饼图的销售仪表盘。点击相关按钮可完成产品的增删改查操作。
实际应用场景
用户分析 :统计不同地区或年龄段的用户分布。
订单监控 :实时展示每日订单量趋势。
销售报表 :多维度对比各品类销售额占比。
关键在于根据业务需求选择合适的图表类型(折线图、柱状图、饼图等),并通过 RESTful API 将数据传递给前端渲染。
'40%'
'70%'
avoidLabelOverlap
false
itemStyle
borderRadius
10
borderColor
'#fff'
borderWidth
2
label
show
false
position
'center'
emphasis
label
show
true
fontSize
20
fontWeight
'bold'
labelLine
show
false
data
<th:block th:each ="product : ${products}" >
{value: <span th:text ="${product.sales}" > </span > , name: '<span th:text ="${product.productName}" > </span > '},
</th:block >
setOption