Spring Boot + Vue.js 大屏可视化平台:从0到1搭建企业级数据驾驶舱(附完整源码)

📅 2026/7/12 10:16:36
Spring Boot + Vue.js 大屏可视化平台:从0到1搭建企业级数据驾驶舱(附完整源码)
Spring Boot Vue.js 大屏可视化平台实战从零构建企业级数据驾驶舱1. 项目架构设计与技术选型企业级数据驾驶舱的核心在于高效的数据处理和直观的视觉呈现。我们采用前后端分离架构后端基于Spring Boot构建RESTful API服务前端使用Vue.js实现动态数据渲染。技术栈对比表组件类型技术选型优势特性前端框架Vue.js 3响应式数据绑定、组合式API、更小的体积和更好的性能可视化库ECharts 5丰富的图表类型、GPU加速渲染、响应式设计状态管理Pinia轻量级、TypeScript支持、DevTools集成后端框架Spring Boot 3自动配置、内嵌服务器、生产级特性数据持久化MyBatis-Plus强大的CRUD操作、Lambda表达式查询、分页插件实时通信WebSocket全双工通信、低延迟数据推送构建工具Vite极速启动、热更新、按需编译后端项目结构src/main/java ├── config # 配置类 ├── controller # REST控制器 ├── service # 业务逻辑层 │ ├── impl # 服务实现 ├── mapper # 数据访问层 ├── entity # 数据实体 ├── dto # 数据传输对象 └── util # 工具类前端项目初始化# 创建Vue项目 npm create vitelatest dashboard-ui --template vue-ts # 安装核心依赖 npm install echarts vue-echarts pinia axios websocket2. 后端数据服务搭建2.1 数据源集成方案针对企业多源数据整合需求我们设计了三层数据接入架构原始数据层直连业务数据库MySQL/Oracle聚合数据层使用Kettle进行ETL处理缓存数据层Redis缓存热点数据多数据源配置示例Configuration MapperScan(basePackages com.demo.mapper.primary, sqlSessionTemplateRef primarySqlSessionTemplate) public class PrimaryDataSourceConfig { Bean(name primaryDataSource) ConfigurationProperties(prefix spring.datasource.primary) public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } Bean(name primarySqlSessionFactory) public SqlSessionFactory sqlSessionFactory(Qualifier(primaryDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean bean new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/primary/*.xml)); return bean.getObject(); } }2.2 核心API开发实时数据接口设计RestController RequestMapping(/api/data) public class DataController { Autowired private DataService dataService; /** * 获取实时指标数据 * param indicator 指标编码 * return 当前指标值及趋势 */ GetMapping(/realtime/{indicator}) public ResponseResultIndicatorVO getRealtimeData( PathVariable String indicator) { return ResponseResult.success( dataService.getIndicatorData(indicator)); } /** * 大数据量分页查询 * param query 查询条件 * return 分页结果 */ PostMapping(/page) public ResponseResultPageResultDataItem getDataByPage( RequestBody DataQuery query) { return ResponseResult.success( dataService.queryByPage(query)); } }性能优化策略添加Cacheable注解实现方法级缓存使用异步处理耗时操作配置连接池参数spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 18000003. 前端可视化实现3.1 大屏布局设计采用响应式栅格系统适配不同分辨率屏幕template div classdashboard-container div classheader-section h1{{ title }}/h1 time-display / /div div classmain-content div classrow div classcol-md-8 chart-card title销售趋势 line-chart :datasalesData / /chart-card /div div classcol-md-4 chart-card title地域分布 map-chart :dataregionData / /chart-card /div /div !-- 更多图表行 -- /div /div /template style scoped .dashboard-container { background-color: #0f1c3c; color: #fff; min-height: 100vh; padding: 20px; } .header-section { display: flex; justify-content: space-between; margin-bottom: 30px; } /style3.2 动态图表组件封装可复用的ECharts组件// components/BaseChart.vue import { use } from echarts/core import { CanvasRenderer } from echarts/renderers import { LineChart, BarChart, PieChart } from echarts/charts import { GridComponent, TooltipComponent, LegendComponent, TitleComponent } from echarts/components use([ CanvasRenderer, LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent, TitleComponent ]) const props defineProps({ option: { type: Object, required: true }, theme: { type: String, default: dark } }) const chartRef refHTMLDivElement() const chart shallowRefecharts.ECharts() onMounted(() { if (chartRef.value) { chart.value echarts.init(chartRef.value, props.theme) chart.value.setOption(props.option) // 响应式调整 const resizeObserver new ResizeObserver(() { chart.value?.resize() }) resizeObserver.observe(chartRef.value) onUnmounted(() { resizeObserver.disconnect() chart.value?.dispose() }) } }) watch(() props.option, (newVal) { chart.value?.setOption(newVal) }, { deep: true })3.3 实时数据更新机制建立WebSocket连接实现数据推送// composables/useWebSocket.ts export function useWebSocket(url: string) { const socket refWebSocket | null(null) const message refany(null) const error refEvent | null(null) const connect () { socket.value new WebSocket(url) socket.value.onopen () { console.log(WebSocket connected) } socket.value.onmessage (event) { message.value JSON.parse(event.data) } socket.value.onerror (err) { error.value err } socket.value.onclose () { console.log(WebSocket disconnected) } } const disconnect () { if (socket.value) { socket.value.close() } } onUnmounted(() { disconnect() }) return { message, error, connect, disconnect } }4. 前后端联调与部署4.1 联调关键步骤接口Mock使用Postman创建API文档跨域处理Spring Boot配置CORSConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(*) .allowedHeaders(*); } }数据格式约定{ code: 200, message: success, data: { xAxis: [1月, 2月, 3月], series: [120, 200, 150] } }4.2 生产环境部署后端部署脚本#!/bin/bash # spring-boot-deploy.sh JAR_NAMEdashboard-api.jar ACTIVE_PROFILEprod # 停止现有服务 PID$(ps -ef | grep $JAR_NAME | grep -v grep | awk {print $2}) if [ -n $PID ]; then kill -9 $PID fi # 启动新服务 nohup java -jar $JAR_NAME --spring.profiles.active$ACTIVE_PROFILE app.log 21 前端Nginx配置server { listen 80; server_name dashboard.example.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }5. 高级功能实现5.1 动态主题切换// stores/theme.ts export const useThemeStore defineStore(theme, { state: () ({ currentTheme: dark, themes: { dark: { backgroundColor: #0f1c3c, textColor: #ffffff, chartTheme: dark }, light: { backgroundColor: #f5f7fa, textColor: #333333, chartTheme: light } } }), actions: { toggleTheme() { this.currentTheme this.currentTheme dark ? light : dark document.documentElement.style.setProperty( --bg-color, this.themes[this.currentTheme].backgroundColor ) } } })5.2 大屏自适应方案// utils/autoResize.js export const useAutoResize (elementRef, onResize) { const resizeObserver new ResizeObserver(entries { const { width, height } entries[0].contentRect onResize({ width, height }) }) onMounted(() { if (elementRef.value) { resizeObserver.observe(elementRef.value) } }) onUnmounted(() { resizeObserver.disconnect() }) } // 组件中使用 const containerRef ref(null) useAutoResize(containerRef, ({ width, height }) { chartInstance.value?.resize({ width, height: height - 40 // 减去标题高度 }) })5.3 性能监控与优化前端性能指标采集// 使用Performance API获取关键指标 const metrics {}; const observe new PerformanceObserver((list) { for (const entry of list.getEntries()) { metrics[entry.name] entry.startTime; } }); observe.observe({ entryTypes: [paint, largest-contentful-paint] }); // 发送统计数据到监控平台 window.addEventListener(load, () { setTimeout(() { navigator.sendBeacon(/api/performance, { fcp: metrics[first-paint], lcp: metrics[largest-contentful-paint], loadTime: window.performance.timing.loadEventEnd - window.performance.timing.navigationStart }); }, 3000); });6. 项目扩展与进阶6.1 微服务架构改造随着业务复杂度提升可将单体应用拆分为数据采集服务负责多源数据接入数据处理服务进行数据清洗转换数据可视化服务提供API接口告警服务监控数据异常Spring Cloud集成// 数据服务注册到Nacos SpringBootApplication EnableDiscoveryClient public class DataServiceApplication { public static void main(String[] args) { SpringApplication.run(DataServiceApplication.class, args); } } // Feign客户端调用 FeignClient(name data-process-service) public interface DataProcessClient { PostMapping(/process) ProcessResult processData(RequestBody RawData data); }6.2 3D可视化集成使用Three.js实现三维数据展示// components/3dChart.vue import * as THREE from three; import { OrbitControls } from three/examples/jsm/controls/OrbitControls; onMounted(() { const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(75, container.value.offsetWidth / container.value.offsetHeight, 0.1, 1000); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.value.offsetWidth, container.value.offsetHeight); container.value.appendChild(renderer.domElement); // 添加3D柱状图 data.value.forEach((item, index) { const height item.value / 10; const geometry new THREE.BoxGeometry(0.8, height, 0.8); const material new THREE.MeshBasicMaterial({ color: new THREE.Color(hsl(${index * 30}, 100%, 50%)) }); const cube new THREE.Mesh(geometry, material); cube.position.x index - data.value.length / 2; cube.position.y height / 2; scene.add(cube); }); camera.position.z 5; const controls new OrbitControls(camera, renderer.domElement); function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); });6.3 移动端适配方案针对移动设备特别优化响应式布局使用CSS媒体查询media (max-width: 768px) { .dashboard-container { padding: 10px; } .chart-card { margin-bottom: 15px; } }手势操作支持// 添加触摸事件支持 const touchStartX ref(0); const onTouchStart (e) { touchStartX.value e.touches[0].clientX; }; const onTouchEnd (e) { const diff e.changedTouches[0].clientX - touchStartX.value; if (Math.abs(diff) 50) { if (diff 0) { // 向右滑动 prevTab(); } else { // 向左滑动 nextTab(); } } };性能优化减少同时显示的图表数量使用轻量级图表替代复杂可视化增加数据采样间隔