前端工程的地址: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;@Overridepublic 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();}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic NumberVO getnumbwer() {Long l = studentMapper.selectCount();Long r = teacherMapper.selectCount();return NumberVO.builder().numberStudent(l).numberTeacher(r).build();}@Overridepublic 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.dataawait 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而不是NaNreturn `${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>