首页 > 其他分享 >SpringBoot+Vue3实现数据可视化大屏

SpringBoot+Vue3实现数据可视化大屏

时间:2024-11-15 13:18:59浏览次数:3  
标签:SpringBoot color Vue3 value height collect rem 大屏 255

前端工程的地址:UserManagerFront: 数据可视化前端 (gitee.com)

效果展示,可以展现出来了,样式可能还有一些丑。

后端代码

后端主要是拿到数据并对数据进行处理,按照前端需要的格式进行返回即可。
import com.njitzx.entity.Student;
import com.njitzx.entity.vo.*;
import com.njitzx.mapper.StudentMapper;
import com.njitzx.mapper.TeacherMapper;
import com.njitzx.serivce.StudentService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
  
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
@Service
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {

    private final StudentMapper studentMapper;

    private final TeacherMapper teacherMapper;

    @Override
    public CollegeVO getCollege() {
        List<Student> students = studentMapper.getAll();

        // 分组并计算每个学院的学生数量
        Map<String, Long> collect = students.stream() //收集成map集合
                .collect(Collectors.groupingBy(Student::getCollege, Collectors.counting()));

        // 根据值进行排序(降序),然后将结果收集到一个新的LinkedHashMap中
        Map<String, Long> sortedCollect = collect.entrySet().stream()
                .sorted(Entry.<String, Long>comparingByValue().reversed())
                .collect(Collectors.toMap(
                        Entry::getKey,
                        Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new
                ));

        // 将排序后的键和值拼接成字符串
        String college = String.join(",", sortedCollect.keySet());
      
        String countList = sortedCollect.values().stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));

        return CollegeVO.builder().nameList(college).numberList(countList).build();
    }

    @Override
    public List<GenderVO> getSex() {
        List<Student> students = studentMapper.getAll();
        Map<String, Long> collect = students.stream()
                .collect(Collectors.groupingBy(Student::getGender, Collectors.counting()));

        List<GenderVO> genderVOList = collect.entrySet().stream()
                .map(entry -> {
                    GenderVO vo = new GenderVO();
                    vo.setName(entry.getKey());
                    vo.setValue(entry.getValue());
                    return vo;
                })
                .collect(Collectors.toList());
        return  genderVOList;
    }

    @Override
    public List<ProvinceVO> getProvince() {
        List<Student> students = studentMapper.getAll();
        Map<String, Long> collect = students.stream()
                .collect(Collectors.groupingBy(Student::getProvinces, Collectors.counting()));
        List<ProvinceVO> genderVOList = collect.entrySet().stream()
                .map(entry -> {
                    ProvinceVO vo = new ProvinceVO();
                    vo.setName(entry.getKey());
                    vo.setValue(entry.getValue());
                    return vo;
                })
                .collect(Collectors.toList());

        return  genderVOList;
    }
    @Override
    public NumberVO getnumbwer() {
        Long l = studentMapper.selectCount();
        Long r = teacherMapper.selectCount();
        return  NumberVO.builder().numberStudent(l).numberTeacher(r).build();
    }

    @Override
    public HobbyVO getHobby() {
        List<Student> students = studentMapper.getAll();

        // 分组并计算每个学院的学生数量
        Map<String, Long> collect = students.stream()
                .collect(Collectors.groupingBy(Student::getHobby, Collectors.counting()));

        // 将排序后的键和值拼接成字符串
        String name = String.join(",", collect.keySet());
        String count = collect.values().stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));

        return HobbyVO.builder().name(name).count(count).build();
    }
}

前端

主要是学习前端如何编写的。

vue3编写echarts代码

1.创建一个盒子<div class="panel bar" ref="chart1"> //通过ref 拿到这个盒子
2 导入echarts import * as echarts from 'echarts';

3const chart1 = ref(null);  创建dom对象

4.创建option配置

5 初始化  let instance = echarts.init(chart1.value);
  
6挂载配置到instance上面 instance.setOption(chartOptions);

  也可以通过document来获取对象,进行初始化和挂载。
    let initMap = echarts.init(document.querySelector('#hobbyRef'))
    initMap.setOption(option)
柱状图的配置
```javascript const chartOptions = { //设置距离边框的样式 grid: { left: '0%', right: '0%', top: "20%", bottom: '4%', containLabel: true }, // 设置标题 title: { // 标题 text: '学院人数排名前五', //居中位置 left: 'center', //设置标题的样式 textStyle: { color: '#2f89cf', // 设置标题颜色为红色 fontSize: 18, // 设置字体大小 fontWeight: 'bold' // 设置字体粗细 } }, color: ['#2f89cf'], tooltip: { trigger: 'axis', //坐标轴点上去触发 axisPointer: {type: 'shadow'} }, xAxis: { type: 'category', data: collegeNameList.value, axisTick: { alignWithLabel: true }, //修改 axisLabel: { color: "rgba(255,255,255,.6)", fontSize: 12, // 调大字体大小 interval: 0, // 强制显示所有标签 rotate: 30, // 旋转标签以避免重叠 formatter: function (value) { // 如果名称超过10个字符,显示省略号 return value.length > 10 ? value.slice(0, 10) + '...' : value; } }, axisLine: { show: false, // 如果想要设置单独的线条样式 lineStyle: { color: "rgba(255,255,255,.1)", width: 1, type: "solid" } } }, yAxis: { type: 'value', axisLabel: { color: "rgba(255,255,255,.6)", fontSize: "12" }, // y轴线条样式 axisLine: { lineStyle: { color: "rgba(255,255,255,.1)", // width: 1, // type: "solid" } }, // y 轴分隔线样式 splitLine: { lineStyle: { color: "rgba(255,255,255,.1)" } } }, //配置数据的 series: [{ name: '学生数量', type: 'bar', barWidth: '35%', data: collegeNumberList.value, itemStyle: { barBorderRadius: 5 } }] }; ```

<h4 id="hA9LP">中国地图</h4>
``javascript
import china from '@/json/china.json'  //导入地图的json数据


const mockData = ref([])

//从后端拿到地图的数据 [{'nane':'北京市','value':500}]
const getmokcData = async () => {
  const res = await getStudentProvince();
  mockData.value = res.data.data
  await nextTick(() => {
    getEcharts3();
  });
}

 let initMap = echarts.init(document.querySelector('#mapDom')); //初始化
  //注册中国地图
 echarts.registerMap('china', china);

//拿到前五的数据
  let topFiveData = mockData.value.sort((a, b) => b.value - a.value).slice(0, 5);
  // 将筛选后的数据转换为 ECharts 需要的格式
  let data = topFiveData.map(i => {
    let cityPosition = getCityPositionByName(i.name);
    return {
      name: i.name,
      value: cityPosition ? [...cityPosition.value.map(Number), i.value] : [0, 0, i.value]
    };
  });
// 创建一个方便查找的字典对象
  let mockDataMap = mockData.value.reduce((acc, item) => {
    acc[item.name] = item.value;
    return acc;
  }, {});

  let options = {
    title: {
      text: '学生家乡分布',
      left: 'center',
      textStyle: {
        color: '#fa4c27',
        fontSize: 18,
        fontWeight: 'bold'
      }
    },
    tooltip: {
      trigger: 'item',
      formatter: (params) => {
        // 从 mockDataMap 中获取对应省份的数据值
        let count = mockDataMap[params.name] || 0; // 确保显示为0而不是NaN
        return `${params.name}<br/>${count} (学生总数)`;
      }
    },
    //又下角的工具
    toolbox: {
      show: true,
      orient: 'vertical',
      left: 'right',
      top: 'center',
      feature: {
        dataView: {readOnly: false, title: '数据视图'},
        restore: {title: '还原'},
        saveAsImage: {title: '保存为图片', pixelRatio: 2}
      }
    },
    visualMap: {
      min: 0,
      max: Math.max(...topFiveData.map(d => d.value)),
      text: ['High', 'Low'],
      realtime: false,
      calculable: true,
      inRange: {
        color: ['#e0f7fa', '#80deea', '#0288d1']
      }
    },
    //地图坐标
    geo: {
      map: 'china',
      roam: false,
      // zoom: 1, // 调整这个值来放大地图
      label: {
        show: false
      },
      emphasis: {
        label: {
          show: false
        }
      }
    },
    series: [
      {
        name: '中国',
        type: 'map',
        map: 'china',
        label: {
          show: false
        },
        data: mockData.value,
      },
      {
        type: 'scatter',
        coordinateSystem: 'geo',
        symbol: 'pin',
        symbolSize: [50, 50],
        label: {
          show: true,
          color: '#fff',
          formatter(value) {
            return value.name + value.data.value[2]; // 显示人数
          },
          fontSize: 10
        },
        itemStyle: {
          color: '#e30707' // 标记颜色
        },
        data: data,
      }
    ]
  };

  initMap.setOption(options);
背景样式
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

li {
  list-style: none;
}

@font-face {
  font-family: electronicFont;
  src: url("@/assets/font/DS-DIGIT.TTF");
}

.header {
  position: relative;
  height: 1.25rem;
  background: url('@/assets/images/head_bg.png') no-repeat top center;
  background-color: #0b1341;
  background-size: 100% 100%;
  h1 {
    font-size: 0.475rem;
    color: #fff;
    text-align: center;
    line-height: 1rem;
  }
  .showTime {
    position: absolute;
    top: 0;
    right: 0.375rem;
    line-height: 0.9375rem;
    font-size: 0.25rem;
    color: rgba(255, 255, 255, 0.7);
  }
}


.mainbox {
  font-family: Arial, Helvetica, sans-serif;
  margin: 0;
  padding: 0;
  /*  背景图定位 / 背景图尺寸  cover 完全铺满容器  contain 完整显示在容器内 */
  background: url('@/assets/images/bg.jpg') no-repeat #000;
  background-size: cover;
  /* 行高是字体1.15倍 */
  line-height: 1.15;
}


.mainbox {
  min-width: 1024px;
  max-width: 1920px;
  padding: 0.125rem 0.125rem 0;
  display: flex;

  .column {
    flex: 3;

    &:nth-child(2) {
      flex: 5;
      margin: 0 0.125rem 0.1875rem;
      overflow: hidden;
    }
  }
}

.panel {
  position: relative;
  height: 3.875rem;
  border: 1px solid rgba(25, 186, 139, 0.17);
  background: rgba(255, 255, 255, 0.04) url('@/assets/images/line.png');
  padding: 0 0.1875rem 0.5rem;
  margin-bottom: 0.1875rem;

  &::before {
    position: absolute;
    top: 0;
    left: 0;
    content: "";
    width: 10px;
    height: 10px;
    border-top: 2px solid #02a6b5;
    border-left: 2px solid #02a6b5;
  }

  &::after {
    position: absolute;
    top: 0;
    right: 0;
    content: "";
    width: 10px;
    height: 10px;
    border-top: 2px solid #02a6b5;
    border-right: 2px solid #02a6b5;
  }

  .panel-footer {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
   //z-index: 0;
    &::before {
      position: absolute;
      bottom: 0;
      left: 0;
      content: "";
      width: 10px;
      height: 10px;
      border-bottom: 2px solid #02a6b5;
      border-left: 2px solid #02a6b5;
    }

    &::after {
      position: absolute;
      bottom: 0;
      right: 0;
      content: "";
      width: 10px;
      height: 10px;
      border-bottom: 2px solid #02a6b5;
      border-right: 2px solid #02a6b5;
    }
  }
  //.panel-footer::before, .panel-footer::after {
  //  z-index: 0; /* 确保伪元素在 panel-footer 下层 */
  //}
  h2 {
    //z-index: 1; /* 确保 panel 在最上层 */
    height: 0.6rem;
    line-height: 0.6rem;
    text-align: center;
    color: #fff;
    font-size: 0.25rem;
    font-weight: 400;

    a {
      margin: 0 0.1875rem;
      color: #fff;
      text-decoration: underline;
    }
  }

  .chart {
    height: 3rem;
  }
}

.no {
  background: rgba(101, 132, 226, 0.1);
  padding: 0.1875rem;

  .no-hd {
    position: relative;
    border: 1px solid rgba(25, 186, 139, 0.17);

    &::before {
      content: "";
      position: absolute;
      width: 30px;
      height: 10px;
      border-top: 2px solid #02a6b5;
      border-left: 2px solid #02a6b5;
      top: 0;
      left: 0;
    }

    &::after {
      content: "";
      position: absolute;
      width: 30px;
      height: 10px;
      border-bottom: 2px solid #02a6b5;
      border-right: 2px solid #02a6b5;
      right: 0;
      bottom: 0;
    }

    ul {
      display: flex;

      li {
        position: relative;
        flex: 1;
        text-align: center;
        height: 1rem;
        line-height: 1rem;
        font-size: 0.875rem;
        color: #ffeb7b;
        padding: 0.05rem 0;
        font-family: electronicFont;
        font-weight: bold;

        &:first-child::after {
          content: "";
          position: absolute;
          height: 50%;
          width: 1px;
          background: rgba(255, 255, 255, 0.2);
          right: 0;
          top: 25%;
        }
      }
    }
  }

  .no-bd ul {
    display: flex;

    li {
      flex: 1;
      height: 0.5rem;
      line-height: 0.5rem;
      text-align: center;
      font-size: 0.225rem;
      color: rgba(255, 255, 255, 0.7);
      padding-top: 0.125rem;
    }
  }
}

.map {
  position: relative;
  height: 10.125rem;

  .chart {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 5;
    height: 10.125rem;
    width: 100%;
  }

  .map1,
  .map2,
  .map3 {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 6.475rem;
    height: 6.475rem;
    background: url('@/assets/images/map.png') no-repeat;
    background-size: 100% 100%;
    opacity: 0.3;
  }

  .map2 {
    width: 8.0375rem;
    height: 8.0375rem;
    background-image: url('@/assets/images/lbx.png');
    opacity: 0.6;
    animation: rotate 15s linear infinite;
    z-index: 2;
  }

  .map3 {
    width: 7.075rem;
    height: 7.075rem;
    background-image: url('@/assets/images/jt.png');
    animation: rotate1 10s linear infinite;
  }

  @keyframes rotate {
    from {
      transform: translate(-50%, -50%) rotate(0deg);
    }
    to {
      transform: translate(-50%, -50%) rotate(360deg);
    }
  }
  @keyframes rotate1 {
    from {
      transform: translate(-50%, -50%) rotate(0deg);
    }
    to {
      transform: translate(-50%, -50%) rotate(-360deg);
    }
  }
}

@media screen and (max-width: 1024px) {
  html {
    font-size: 42px !important;
  }
}

@media screen and (min-width: 1920px) {
  html {
    font-size: 80px !important;
  }
}

template

整体结构是 flex布局 3 5 3 布局样式。

<template>
  
  <div class="header">
    <h1>工程数据分析</h1>
    <div class="showTime"></div>
  </div>

  
  <!-- 页面主体  一个大的盒子  划分 -->
  <div class="mainbox">
    <div class="column">
      <div class="panel bar" ref="chart1">
        <div class="chart"></div>
        <!--        <h2 style="color: red">学院人数排名前五</h2>-->
        <div class="panel-footer"></div>
      </div>
      <div class="panel line" ref="chart2">
        <div class="chart"></div>
        <h2>标题</h2>
        <div class="panel-footer"></div>
      </div>
      <div class="panel pie">
        <h2></h2>
        <div class="panel-footer"></div>
      </div>
    </div>


    <div class="column">
      <div class="no">
        <div class="no-hd">
          <ul>
            <li>{{ stNumber.numberTeacher }}</li>
            <li>{{ stNumber.numberStudent }}</li>
          </ul>
        </div>
        <div class="no-bd">
          <ul>
            <li>学生人数</li>
            <li>老师人数</li>
          </ul>
        </div>
      </div>
      <!-- 地图模块 -->
      <div class="map">
        <!-- 放到map中间 -->
        <div class="chart" id="mapDom"></div>
        <div class="map1"></div>
        <div class="map2"></div>
        <div class="map3"></div>
      </div>
    </div>


    <div class="column">
      <div class="panel bar" >
        <h2></h2>
        <div class="chart" id="hobbyRef"></div>
        <div class="panel-footer"></div>
      </div>
      <div class="panel line">
        <h2></h2>

        <div class="panel-footer"></div>
      </div>
      <div class="panel pie">
        <h2></h2>
        <div class="panel-footer"></div>
      </div>

    </div>
  </div>
</template>

标签:SpringBoot,color,Vue3,value,height,collect,rem,大屏,255
From: https://blog.csdn.net/ngczx/article/details/143793377

相关文章

  • 201_springboot基于协同过滤的就业推荐系统
    目录系统展示开发背景代码实现项目案例 获取源码博主介绍:CodeMentor毕业设计领航者、全网关注者30W+群落,InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者,博客领航之星、开发者头条/腾讯云/AWS/Wired等平台优选内容创作者、深耕Web......
  • Springboot餐饮管理系统设计与实现d9u1u(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,员工,菜品信息,菜品分类,菜品订单,餐厅餐桌,订座信息,食材信息,供应商,出库信息,入库信息,食材盘点,食材采购开题报告内容一、研究背景随着餐饮行业的快速......
  • Springboot彩妆专卖分享平台的设计与实现31682(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,美妆教程,美妆分类,美妆产品,美妆知识开题报告内容一、研究背景与意义随着社交媒体的兴起和人们对美的追求,彩妆已经成为现代女性生活中不可或缺的一部分。......
  • 基于springboot+vue实现的摄影师分享交流社区 (源码+L文+ppt)4-094
      3.1系统功能结构3.2.2数据逻辑结构(共13张表)在综合了以上对系统的设计,和对数据库的分析,同时结合了以往对数据库的使用经验后,构建了以下几张表,以下为每张表的内容:表3-1:用户字段名称类型长度字段说明主键默认值idbigint主键  主键addtimeti......
  • 基于springboot+vue实现的人事管理系统 (源码+L文+ppt)4-084
      摘 要人事管理系统是企业用以维护员工信息、管理人力资源活动和优化组织效能的关键工具。该系统具备多项功能,包括员工信息管理、部门协调、培训安排、职位调整、考勤追踪、请假审批、奖惩记录、工资发放以及员工积分管理等。管理员端提供全面控制,允许系统管理员执行从......
  • 基于springboot+vue实现的摄影师分享交流社区 (源码+L文+ppt)4-094
      3.1系统功能结构3.2.2数据逻辑结构(共13张表)在综合了以上对系统的设计,和对数据库的分析,同时结合了以往对数据库的使用经验后,构建了以下几张表,以下为每张表的内容:表3-1:用户字段名称类型长度字段说明主键默认值idbigint主键  主键addtimeti......
  • 基于springboot+vue实现的人事管理系统 (源码+L文+ppt)4-084
      摘 要人事管理系统是企业用以维护员工信息、管理人力资源活动和优化组织效能的关键工具。该系统具备多项功能,包括员工信息管理、部门协调、培训安排、职位调整、考勤追踪、请假审批、奖惩记录、工资发放以及员工积分管理等。管理员端提供全面控制,允许系统管理员执行从......
  • 基于springboot+vue实现的摄影师分享交流社区 (源码+L文+ppt)4-094
      3.1系统功能结构3.2.2数据逻辑结构(共13张表)在综合了以上对系统的设计,和对数据库的分析,同时结合了以往对数据库的使用经验后,构建了以下几张表,以下为每张表的内容:表3-1:用户字段名称类型长度字段说明主键默认值idbigint主键  主键addtimeti......
  • 基于springboot+vue实现的大型超市数据处理系统 (源码+L文+ppt)4-015
      第4章 系统设计本章主要讲述的是大型超市数据处理系统的设计开发结构,简单介绍了开发流程与数据库设计的原则以及数据表的关系结构图,并且详细的展示了数据表的内部结构信息与属性。图4-2大型超市数据处理系统总体结构图4.4 数据表信息(共18张表)在关系数据E-R图中,......
  • 基于springboot+vue实现的高校电子图书馆的大数据平台 (源码+L文+ppt)4-013
      4.1系统结构设计这些功能可以充分满足高校电子图书馆的大数据平台的需求。此系统功能较为全面如下图系统功能结构如图4-1所示。图4-1功能结构图4.3.2 数据库表结构(共15张表)本论文中的高校电子图书馆的大数据平台采用MySQL数据库,系统中的所有对象以及对象的所有属性......