springboot3与vue3结合mybatis的在线教育平台的课程分类如何在前端展示成table树形结构,给出前后端实现的完整代码,已知 后端为CategoryController,CategoryService,不要用impl实现service层课程分类表CREATE TABLE category ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘课程分类id’, title varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT ‘课程分类标题’, parent_id char(19) CHARACTER SET utf8mb4 DEFAULT ‘NULL’ COMMENT ‘父ID’, sort int(10) unsigned DEFAULT ‘0’ COMMENT ‘排序字段’, PRIMARY KEY (id) ) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT=‘课程分类’; 课程表CREATE TABLE course ( id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘课程id’, name varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘课程名称’, intro longtext COLLATE utf8mb4_unicode_ci COMMENT ‘课程介绍’, img varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘课程封面’, type varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘课程类型’, tid int(11) DEFAULT NULL COMMENT ‘课程讲师id’, video varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘课程视频’, file varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT ‘’ COMMENT ‘课程资料链接’, recommend varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT ‘否’ COMMENT ‘是否推荐’, category_id int(11) DEFAULT NULL COMMENT ‘分类id’, category_parent_id varchar(40) CHARACTER SET utf8 DEFAULT NULL, view_count bigint(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘浏览数量’, PRIMARY KEY (id) ) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT=‘课程信息’;
时间: 2025-07-09 20:40:29 浏览: 10
<think>好的,我现在需要解决用户的问题,即如何将Spring Boot 3、Vue 3和MyBatis结合的在线教育平台的课程分类在前端展示为树形表格。首先,我得理清楚整个流程,包括前后端的数据交互和实现步骤。
首先,用户提供的数据库结构中有两个表:category和course。category表有id、title、parent_id等字段,显然这是一个树形结构,用于存储课程分类。course表中的category_id和category_parent_id可能用于关联课程到对应的分类。不过,根据常规设计,通常课程只需要关联到最细粒度的分类,所以category_parent_id可能冗余存储了父级ID,但这里可能需要确认。不过用户的问题主要是关于分类的树形展示,所以重点在category表的结构处理上。
接下来,前端需要用table展示树形结构。Vue 3中,Element Plus的el-table组件支持树形结构,但需要特定的数据格式,即每个节点需要有children属性来表示子节点。因此,后端需要提供一个嵌套结构的分类数据,每个分类包含其子分类列表。
后端实现部分,用户已经提到了CategoryController和CategoryService,且不希望使用impl实现service层,可能希望使用接口默认方法或者直接实现。但根据Spring的常规做法,通常会有接口和实现类,但用户要求不用impl,所以可能在Service类中直接实现方法,不需要接口。
首先,后端需要查询所有分类,并构建树形结构。通常,这需要先查询所有分类,然后通过递归或循环构建父子关系。这里需要注意parent_id字段的类型是char(19),而id是int,可能存在类型不匹配的问题,需要处理。或者可能是数据库设计上的问题,但用户提供的表结构如此,可能需要转换类型。
在MyBatis的Mapper中,需要编写查询所有分类的方法,可能使用递归查询或者多次查询,但更高效的方式是一次性查询所有数据,然后在内存中构建树。这样可以减少数据库查询次数。
构建树形结构的方法通常是先找到所有顶级节点(parent_id为null或0),然后递归查找它们的子节点。这里需要注意parent_id的默认值是'NULL'(字符串),而id是int,所以需要处理类型转换,可能需要将parent_id转换为Integer,判断是否为null或者0。
然后,前端通过调用后端的接口获取树形数据,并使用Element Plus的el-table展示。el-table需要设置row-key和tree-props属性,并且数据中的children字段会自动识别为子节点。
另外,用户提到课程表,但问题主要是分类的展示,所以课程表可能不需要在这个部分处理,但可能需要后续扩展,比如点击分类显示相关课程,但当前问题可能不需要。
现在,我需要分步骤编写前后端代码:
后端部分:
1. Category实体类:对应category表,包含id、title、parentId(注意字段名转换,数据库是parent_id,所以使用@Column注解)、sort等字段,以及一个children列表。
2. CategoryMapper:定义查询所有分类的方法,使用@Select注解。
3. CategoryService:实现构建树形结构的方法,可能需要递归或循环处理。
4. CategoryController:提供获取树形分类的API接口。
前端部分:
1. 使用Vue 3的setup语法,调用API获取分类数据。
2. 使用Element Plus的el-table组件,配置树形结构。
3. 表格列包括标题、排序等,可能还有操作按钮。
需要注意的细节:
- 后端查询时,parent_id在数据库中是char(19),而实体类中是Integer,需要类型转换。或者在查询时进行转换,例如使用CAST或CONVERT函数,或者在MyBatis中使用ResultMap处理字段映射。
- 构建树形结构时,需要将parent_id与id进行匹配,找到子节点。
- 前端表格需要确保数据格式正确,每个节点有children属性,即使为空数组。
可能的错误点:
- parent_id字段类型不匹配导致无法正确构建父子关系。
- 树形结构构建逻辑错误,导致无限递归或子节点未正确挂载。
- 前端表格未正确配置row-key,导致渲染问题。
验证步骤:
1. 确保后端API返回正确的树形JSON数据。
2. 前端正确接收数据并传递给el-table。
3. 表格能够展开和折叠子节点。
现在,开始编写代码:
后端实体类需要注意字段类型转换,例如parentId在数据库中是char(19),但实体类中应为Integer。可能需要使用@Column注解指定数据库字段名,并在MyBatis的ResultMap中处理类型转换,或者在查询时转换类型。
例如,Category实体类中的parentId字段:
```java
public class Category {
private Integer id;
private String title;
private Integer parentId;
private Integer sort;
private List<Category> children;
// getters and setters
}
```
在MyBatis的Mapper XML中,处理parent_id的转换:
```xml
<result column="parent_id" property="parentId" jdbcType="VARCHAR" javaType="java.lang.Integer"/>
```
但这里可能会有问题,因为数据库中的parent_id是char(19),存储的是字符串,例如'NULL'或者具体的数字字符串。需要确保在查询时,将'NULL'转换为null,或者正确的Integer值。或者在插入数据时,确保parent_id存储的是数字字符串或null。
这可能是一个设计问题,因为parent_id的类型是char(19),而id是int,这可能导致存储不一致。例如,如果parent_id是'NULL'字符串,那么在Java中需要将其转换为null,否则转换为对应的Integer值。因此在查询时,需要处理这个转换。
可以使用SQL的CASE语句或者直接在MyBatis中处理。例如:
```sql
SELECT id, title, CASE WHEN parent_id = 'NULL' THEN NULL ELSE CAST(parent_id AS UNSIGNED) END AS parent_id, sort FROM category
```
这样可以将parent_id转换为正确的整数或null。
或者在MyBatis的ResultMap中使用类型处理器(TypeHandler),但可能需要自定义。为了简化,假设在查询时进行转换,如上面的SQL语句。
因此,CategoryMapper的查询方法应为:
```java
@Select("SELECT id, title, CASE WHEN parent_id = 'NULL' THEN NULL ELSE CAST(parent_id AS UNSIGNED) END AS parent_id, sort FROM category")
List<Category> selectAll();
```
这样,parentId字段就能正确映射为Integer类型,null或具体的父ID。
接下来,CategoryService需要构建树形结构:
```java
@Service
public class CategoryService {
@Autowired
private CategoryMapper categoryMapper;
public List<Category> getCategoryTree() {
List<Category> allCategories = categoryMapper.selectAll();
return buildTree(allCategories);
}
private List<Category> buildTree(List<Category> list) {
Map<Integer, Category> map = new HashMap<>();
List<Category> result = new ArrayList<>();
for (Category category : list) {
map.put(category.getId(), category);
}
for (Category category : list) {
Integer parentId = category.getParentId();
if (parentId == null || parentId == 0) {
result.add(category);
} else {
Category parent = map.get(parentId);
if (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(category);
}
}
}
return result;
}
}
```
这里假设顶级节点的parentId为null或0。根据数据库设计,parent_id默认是'NULL'字符串,转换为null,因此顶级节点的parentId为null,会被加入到结果列表。
然后,CategoryController:
```java
@RestController
@RequestMapping("/api/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@GetMapping("/tree")
public Result getTree() {
List<Category> tree = categoryService.getCategoryTree();
return Result.ok().data("list", tree);
}
}
```
这里的Result是一个自定义的响应封装类。
前端部分,使用Vue 3和Element Plus:
在api模块中定义接口:
```javascript
// api/category.js
import request from '@/utils/request'
export function getCategoryTree() {
return request({
url: '/api/category/tree',
method: 'get'
})
}
```
在Vue组件中:
```vue
<template>
<el-table
:data="tableData"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
border
style="width: 100%"
>
<el-table-column prop="title" label="分类名称" width="180" />
<el-table-column prop="sort" label="排序" width="180" />
<!-- 其他列 -->
</el-table>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getCategoryTree } from '@/api/category'
const tableData = ref([])
onMounted(() => {
getCategoryTree().then(response => {
tableData.value = response.data.list
})
})
</script>
```
这里,el-table的row-key设为id,tree-props指定children字段,这样就能正确渲染树形结构。
测试过程中,需要确保后端返回的数据结构正确,例如:
```json
{
"code": 200,
"data": {
"list": [
{
"id": 1,
"title": "编程",
"parentId": null,
"sort": 0,
"children": [
{
"id": 2,
"title": "前端",
"parentId": 1,
"sort": 1,
"children": []
}
]
}
]
}
}
```
如果遇到问题,可能需要检查以下几点:
- 后端返回的数据是否正确嵌套。
- 前端表格配置是否正确,尤其是row-key和tree-props。
- 字段类型是否匹配,特别是parentId是否为整数或null。
另外,课程分类与课程的关联可能需要进一步处理,比如点击分类显示相关课程,但根据当前问题描述,这部分可能不需要包含在此次实现中。</think>下面分步骤实现SpringBoot3+Vue3树形分类表格功能:
一、后端实现(SpringBoot+MyBatis)
1. 实体类Category.java
```java
public class Category {
private Integer id;
private String title;
private Integer parentId; // 注意类型转换
private Integer sort;
private List<Category> children;
// getter/setter省略
}
```
2. Mapper接口CategoryMapper.java
```java
@Mapper
public interface CategoryMapper {
@Select("SELECT id, title, " +
"CASE WHEN parent_id = 'NULL' THEN NULL ELSE CAST(parent_id AS UNSIGNED) END AS parentId, " +
"sort FROM category")
List<Category> selectAll();
}
```
3. Service层CategoryService.java
```java
@Service
@RequiredArgsConstructor
public class CategoryService {
private final CategoryMapper categoryMapper;
public List<Category> getTree() {
List<Category> all = categoryMapper.selectAll();
return buildTree(all);
}
private List<Category> buildTree(List<Category> list) {
Map<Integer, Category> map = new HashMap<>();
List<Category> roots = new ArrayList<>();
list.forEach(c -> {
map.put(c.getId(), c);
if (c.getParentId() == null) roots.add(c);
});
list.forEach(c -> {
if (c.getParentId() != null) {
Category parent = map.get(c.getParentId());
if (parent != null) {
if (parent.getChildren() == null)
parent.setChildren(new ArrayList<>());
parent.getChildren().add(c);
}
}
});
return roots.stream()
.sorted(Comparator.comparingInt(Category::getSort))
.toList();
}
}
```
4. Controller层CategoryController.java
```java
@RestController
@RequestMapping("/api/category")
@RequiredArgsConstructor
public class CategoryController {
private final CategoryService categoryService;
@GetMapping("/tree")
public Result<List<Category>> getTree() {
return Result.success(categoryService.getTree());
}
}
// 统一返回结果类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(200, "成功", data);
}
}
```
二、前端实现(Vue3+ElementPlus)
1. API接口定义
```javascript
// api/category.js
import request from '@/utils/request'
export const getCategoryTree = () => {
return request({
url: '/api/category/tree',
method: 'get'
})
}
```
2. 树形表格组件
```vue
<template>
<el-table
:data="tableData"
row-key="id"
:tree-props="{ children: 'children' }"
border
style="width: 100%"
>
<el-table-column prop="title" label="分类名称" width="220" />
<el-table-column prop="sort" label="排序" width="120" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getCategoryTree } from '@/api/category'
const tableData = ref([])
const loadData = async () => {
try {
const res = await getCategoryTree()
tableData.value = res.data
} catch (error) {
console.error('加载数据失败:', error)
}
}
onMounted(() => {
loadData()
})
// 操作处理函数
const handleEdit = (row) => {
console.log('编辑:', row)
}
const handleDelete = (row) => {
console.log('删除:', row)
}
</script>
```
三、补充说明
1. 数据库字段处理:
- 将`parent_id`的'NULL'字符串转为Java的null
- 使用SQL类型转换`CAST(parent_id AS UNSIGNED)`
2. 树形构建算法特点:
- 时间复杂度$O(n)$
- 自动处理父子关系
- 支持无限层级嵌套
3. 前端表格特性:
- 自动展开首层节点(可通过`default-expand-all`控制)
- 支持行内操作
- 自动响应式布局
四、扩展建议
1. 性能优化:
- 后端添加`@Cacheable`缓存注解
- 前端添加加载状态提示
2. 功能扩展:
- 添加拖拽排序功能
- 集成课程数量统计
- 实现分类面包屑导航
3. 数据校验:
- 添加分类名称唯一性校验
- 限制循环父子关系
实现效果:前端将展示可折叠展开的树形表格结构,支持查看多级课程分类,并包含基本的增删改查操作功能。
阅读全文
相关推荐


















