动态图表 -- eg1
问题:
前端vue,后端springboot,实现动态表格样式,(表格List<Student>,Student类有年级,班级,文理科分类,姓名,学号,等属性。先根据年级分类统计作为第一列,同个年级的合并,再根据文理科分类统计作为第二列,同样分类的合并,最后根据班级分类统计,同个班级的合并行,第四列是学生学号,第五列是学生姓名。第一行是合并5列的学校名称,第二行是合并3个格子的“分班信息”,合并2各格子的“学生信息”。学生信息的那2列要能让前端点击任一那两列的格子时带信息跳转,跳转信息包括(学生学号,年级,班级,文理科分类)。以上信息统一由后端返回给前端。表格列数固定,行数不固定,其中,合并行的合并行数或合并列的合并列数,会根据具体数据动态计算),提供完整的前后端代码
动态表格实现(Vue + SpringBoot)
下面我将提供一个完整的解决方案,包括前端Vue和后端SpringBoot的实现。
后端SpringBoot实现
1. 实体类定义
java
// Student.java
public class Student {private String grade; // 年级private String className; // 班级private String subjectType; // 文理科分类private String name; // 姓名private String studentId; // 学号// 构造方法、getter和setterpublic Student() {}public Student(String grade, String className, String subjectType, String name, String studentId) {this.grade = grade;this.className = className;this.subjectType = subjectType;this.name = name;this.studentId = studentId;}// getter和setter方法// ...
}
2. 表格数据结构
java
// TableRow.java
public class TableRow {private String grade; // 年级private String subjectType; // 文理科分类private String className; // 班级private String studentId; // 学号private String studentName; // 姓名private int gradeRowSpan; // 年级合并行数private int subjectRowSpan; // 文理科合并行数private int classRowSpan; // 班级合并行数// 构造方法、getter和setter// ...
}
3. 控制器和服务层
java
// StudentController.java
@RestController
@RequestMapping("/api/students")
public class StudentController {@Autowiredprivate StudentService studentService;@GetMapping("/table-data")public ResponseEntity<List<TableRow>> getTableData() {List<TableRow> tableData = studentService.generateTableData();return ResponseEntity.ok(tableData);}
}
java
// StudentService.java
@Service
public class StudentService {// 模拟数据 - 实际应用中可以从数据库获取private List<Student> mockStudents = Arrays.asList(new Student("高一", "一班", "理科", "张三", "2023001"),new Student("高一", "一班", "理科", "李四", "2023002"),new Student("高一", "二班", "文科", "王五", "2023003"),new Student("高二", "三班", "理科", "赵六", "2023004"),new Student("高二", "三班", "理科", "钱七", "2023005"),new Student("高二", "四班", "文科", "孙八", "2023006"),new Student("高三", "五班", "理科", "周九", "2023007"),new Student("高三", "六班", "文科", "吴十", "2023008"));public List<TableRow> generateTableData() {// 1. 按年级、文理科、班级分组Map<String, Map<String, Map<String, List<Student>>>> groupedData = mockStudents.stream().collect(Collectors.groupingBy(Student::getGrade,Collectors.groupingBy(Student::getSubjectType,Collectors.groupingBy(Student::getClassName))));// 2. 构建表格行数据List<TableRow> tableRows = new ArrayList<>();groupedData.forEach((grade, subjectMap) -> {// 计算年级合并行数int gradeCount = subjectMap.values().stream().mapToInt(classMap -> classMap.size()).sum();subjectMap.forEach((subjectType, classMap) -> {// 计算文理科合并行数int subjectCount = classMap.size();classMap.forEach((className, students) -> {// 每个学生或空行(用于合并)if (students.isEmpty()) {// 空行处理(如果有需要)} else {// 添加学生行students.forEach(student -> {TableRow row = new TableRow();row.setGrade(grade);row.setSubjectType(subjectType);row.setClassName(className);row.setStudentId(student.getStudentId());row.setStudentName(student.getName());// 计算合并行数(只在第一行设置)if (tableRows.isEmpty() || !tableRows.get(tableRows.size() - 1).getGrade().equals(grade)) {row.setGradeRowSpan(countGradeRows(groupedData, grade));}if (tableRows.isEmpty() || !tableRows.get(tableRows.size() - 1).getGrade().equals(grade) || !tableRows.get(tableRows.size() - 1).getSubjectType().equals(subjectType)) {row.setSubjectRowSpan(countSubjectRows(groupedData, grade, subjectType));}if (tableRows.isEmpty() || !tableRows.get(tableRows.size() - 1).getGrade().equals(grade) || !tableRows.get(tableRows.size() - 1).getSubjectType().equals(subjectType) || !tableRows.get(tableRows.size() - 1).getClassName().equals(className)) {row.setClassRowSpan(students.size());}tableRows.add(row);});}});});});return tableRows;}private int countGradeRows(Map<String, Map<String, Map<String, List<Student>>>> groupedData, String grade) {return groupedData.get(grade).values().stream().mapToInt(Map::size).sum();}private int countSubjectRows(Map<String, Map<String, Map<String, List<Student>>>> groupedData, String grade, String subjectType) {return groupedData.get(grade).get(subjectType).size();}
}
前端Vue实现
1. 安装依赖
确保已安装Vue和axios:
bash
npm install axios
2. 表格组件实现
vue
<template><div class="table-container"><table class="dynamic-table"><!-- 表头 --><thead><tr><th colspan="5" class="school-header">学校名称</th></tr><tr><th colspan="3" class="info-header">分班信息</th><th colspan="2" class="student-header">学生信息</th></tr><tr><th>年级</th><th>文理科</th><th>班级</th><th>学号</th><th>姓名</th></tr></thead><!-- 表格内容 --><tbody><template v-for="(row, index) in tableData" :key="index"><tr><!-- 年级列 --><td v-if="shouldShowGrade(index)" :rowspan="row.gradeRowSpan">{{ row.grade }}</td><!-- 文理科列 --><td v-if="shouldShowSubject(index)" :rowspan="row.subjectRowSpan">{{ row.subjectType }}</td><!-- 班级列 --><td v-if="shouldShowClass(index)" :rowspan="row.classRowSpan">{{ row.className }}</td><!-- 学号列 --><td @click="handleStudentClick(row)">{{ row.studentId }}</td><!-- 姓名列 --><td @click="handleStudentClick(row)">{{ row.studentName }}</td></tr></template></tbody></table><!-- 学生详情弹窗 --><div v-if="showStudentDetail" class="student-detail-modal"><div class="modal-content"><span class="close" @click="showStudentDetail = false">×</span><h3>学生详细信息</h3><p>学号: {{ selectedStudent.studentId }}</p><p>年级: {{ selectedStudent.grade }}</p><p>班级: {{ selectedStudent.className }}</p><p>文理科: {{ selectedStudent.subjectType }}</p></div></div></div>
</template><script>
import axios from 'axios';export default {name: 'DynamicTable',data() {return {tableData: [],showStudentDetail: false,selectedStudent: {}};},created() {this.fetchTableData();},methods: {fetchTableData() {axios.get('/api/students/table-data').then(response => {this.tableData = response.data;// 计算合并行数this.calculateRowSpans();}).catch(error => {console.error('获取表格数据失败:', error);});},calculateRowSpans() {// 这里可以在后端已经计算好,前端直接使用// 如果需要在前端计算,可以在这里实现},shouldShowGrade(index) {// 如果是该年级的第一行,显示年级单元格if (index === 0) return true;const currentGrade = this.tableData[index].grade;const prevGrade = this.tableData[index - 1].grade;return currentGrade !== prevGrade;},shouldShowSubject(index) {// 如果是该文理科的第一行,显示文理科单元格if (index === 0) return true;const currentGrade = this.tableData[index].grade;const prevGrade = this.tableData[index - 1].grade;const currentSubject = this.tableData[index].subjectType;const prevSubject = this.tableData[index - 1].subjectType;return currentGrade !== prevGrade || currentSubject !== prevSubject;},shouldShowClass(index) {// 如果是该班级的第一行,显示班级单元格if (index === 0) return true;const currentGrade = this.tableData[index].grade;const prevGrade = this.tableData[index - 1].grade;const currentSubject = this.tableData[index].subjectType;const prevSubject = this.tableData[index - 1].subjectType;const currentClass = this.tableData[index].className;const prevClass = this.tableData[index - 1].className;return currentGrade !== prevGrade || currentSubject !== prevSubject || currentClass !== prevClass;},handleStudentClick(student) {this.selectedStudent = {studentId: student.studentId,grade: student.grade,className: student.className,subjectType: student.subjectType};this.showStudentDetail = true;}}
};
</script><style scoped>
.table-container {width: 100%;overflow-x: auto;
}.dynamic-table {width: 100%;border-collapse: collapse;margin: 20px 0;
}.dynamic-table th, .dynamic-table td {border: 1px solid #ddd;padding: 8px;text-align: center;
}.dynamic-table th {background-color: #f2f2f2;font-weight: bold;
}.school-header {background-color: #4CAF50;color: white;font-size: 1.2em;
}.info-header {background-color: #2196F3;color: white;
}.student-header {background-color: #FF9800;color: white;
}.dynamic-table tr:nth-child(even) {background-color: #f9f9f9;
}.dynamic-table tr:hover {background-color: #f1f1f1;cursor: pointer;
}.student-detail-modal {display: block;position: fixed;z-index: 1;left: 0;top: 0;width: 100%;height: 100%;background-color: rgba(0,0,0,0.4);
}.modal-content {background-color: #fefefe;margin: 10% auto;padding: 20px;border: 1px solid #888;width: 50%;border-radius: 5px;
}.close {color: #aaa;float: right;font-size: 28px;font-weight: bold;cursor: pointer;
}.close:hover {color: black;
}
</style>
3. 主应用入口
javascript
// main.js
import Vue from 'vue';
import App from './App.vue';
import axios from 'axios';Vue.config.productionTip = false;// 设置axios基础URL
axios.defaults.baseURL = 'http://localhost:8080'; // 根据实际后端地址调整new Vue({render: h => h(App),
}).$mount('#app');
完整实现说明
- 后端部分:
- 定义了
Student
实体类存储学生信息 - 创建了
TableRow
类来表示表格行数据,包含合并行数信息 StudentService
处理业务逻辑,将学生数据按年级、文理科、班级分组- 计算每个分组的合并行数
- 提供REST API接口返回表格数据
- 定义了
- 前端部分:
- 使用Vue组件展示动态表格
- 通过axios从后端获取表格数据
- 实现合并单元格逻辑(年级、文理科、班级列)
- 学生信息列添加点击事件,显示学生详情弹窗
- 添加了表格样式,使界面更美观
- 合并单元格逻辑:
- 后端已经计算好每个单元格的rowspan值
- 前端通过
shouldShowGrade
、shouldShowSubject
、shouldShowClass
方法判断是否显示单元格 - 使用
rowspan
属性实现单元格合并
- 交互功能:
- 点击学号或姓名列会弹出学生详情信息
- 详情信息包括学号、年级、班级和文理科分类
扩展建议
- 可以添加排序功能,允许用户按不同列排序
- 可以添加分页功能,处理大量数据
- 可以添加搜索功能,快速定位特定学生
- 可以添加导出功能,将表格导出为Excel或PDF
这个实现提供了完整的动态表格功能,包括合并单元格、点击交互和样式美化。根据实际需求,您可以进一步调整和扩展。