SpringBoot3 + Vue3 + Element-Plus + TS 实现动态二级菜单选择器
- 1、效果展示
-
- [1.1 点击效果](#1.1 点击效果)
- [1.2 选择效果](#1.2 选择效果)
- [1.3 返回值](#1.3 返回值)
- [1.4 模拟后端返回数据](#1.4 模拟后端返回数据)
- 2、前端代码
-
- [2.1 UnusedList.vue](#2.1 UnusedList.vue)
- [2.2 goodsType.ts](#2.2 goodsType.ts)
- [2.3 http.ts](#2.3 http.ts)
- 3、后端代码
-
- [3.1 GoodsCategoryController.java](#3.1 GoodsCategoryController.java)
- [3.2 GoodsCategoryService.java](#3.2 GoodsCategoryService.java)
- [3.3 GoodsCategoryServiceImpl.java](#3.3 GoodsCategoryServiceImpl.java)
1、效果展示
1.1 点击效果
1.2 选择效果
1.3 返回值
返回值为二级分类的 id
json
{
categoryId: "21"
}
1.4 模拟后端返回数据
java
const categories =
[
[
{ "id": 9, "name": "吃的" },
{ "id": 5, "name": "食品" },
{ "id": 4, "name": "数码" },
{ "id": 1, "name": "服饰" }
],
[
{ "id": 18, "name": "相机", "categoryFatherId": 4 },
{ "id": 17, "name": "电脑", "categoryFatherId": 4 },
{ "id": 14, "name": "裤子", "categoryFatherId": 1 },
{ "id": 19, "name": "零食", "categoryFatherId": 5 },
{ "id": 16, "name": "手机", "categoryFatherId": 4 },
{ "id": 20, "name": "牛奶", "categoryFatherId": 5 },
{ "id": 21, "name": "辣条", "categoryFatherId": 5 },
{ "id": 1, "name": "衣服", "categoryFatherId": 1 },
{ "id": 15, "name": "裙子", "categoryFatherId": 1 },
{ "id": 23, "name": "可乐", "categoryFatherId": 9 }
]
];
2、前端代码
2.1 UnusedList.vue
js
<template>
<el-form-item prop="categoryId" label="商品分类:">
<el-cascader :options="cascaderOptions" @change="handleCascaderChange" style="width: 600px">
<template #default="{ node, data }">
<span>{{ data.label }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</template>
<script setup lang="ts">
import { Ref, computed, onMounted, reactive, ref } from 'vue';
import { getSelectListApi } from '@/api/goods/goodsType.ts'
// 新增表单内容
const addGoodParm = reactive({
categoryId: "",
})
// 定义 Ref 类型的数组
const categories = ref<any[]>([]);
// 获取所有分类
const getAllShopType = async () => {
let res = await getSelectListApi();
categories.value = res.data;
}
// 动态计算二级分类
const cascaderOptions = computed(() => {
if (!categories.value || categories.value.length !== 2) {
return [];
}
const [mainCategories, subCategories] = categories.value;
// 根据一级分类和二级分类动态生成 options 数据
return mainCategories.map((mainCategory: { id: { toString: () => any; }; name: any; }) => {
const children = subCategories.filter((subCategory: { categoryFatherId: { toString: () => any; }; }) => subCategory.categoryFatherId === mainCategory.id)
.map((subCategory: { id: { toString: () => any; }; name: any; }) => ({
value: subCategory.id.toString(),
label: subCategory.name,
}));
return {
value: mainCategory.id.toString(),
label: mainCategory.name,
children,
};
});
});
// 处理 el-cascader 的 change 事件
// 设置商品分类id
const handleCascaderChange = (value: string[]) => {
addGoodParm.categoryId = value[1];
console.log( addGoodParm);
</script>
onMounted(() => {
getAllShopType();
})
2.2 goodsType.ts
ts
import http from "@/http/http.ts";
// 查询所有分类结构化的返回
export const getSelectListApi = () => {
return http.get(`/api/goodsCategory/getSelectList`);
}
2.3 http.ts
java
/*
* @Date: 2024-03-30 12:37:05
* @LastEditors: zhong
* @LastEditTime: 2024-04-16 20:27:33
* @FilePath: \app-admin\src\http\http.ts
*/
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import { ElMessage } from "element-plus";
// axios 请求配置
const config = {
// baseURL:'http://localhost:8080',
baseURL: '/api',
timeout: 1000
}
// 定义返回值类型
export interface Result<T = any> {
code: number;
msg: string;
data: T;
}
class Http {
// axios 实例
private instance: AxiosInstance;
// 构造函数初始化
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config);
//定义拦截器
this.interceptors();
}
private interceptors() {
// axios 发送请求之前的处理
this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
// 在请求头部携带token
// let token = sessionStorage.getItem('token');
let token = '';
if (token) {
config.headers!['token'] = token;
// 把 token 放到 headers 里面
// (config.headers as AxiosRequestHeaders).token = token;
}
// console.log(config);
return config;
}, (error: any) => {
error.data = {};
error.data.msg = '服务器异常,请联系管理员!'
return error;
})
// axios 请求返回之后的处理
// 请求返回处理
this.instance.interceptors.response.use((res: AxiosResponse) => {
// console.log(res.data);
if (res.data.code != 200) {
ElMessage.error(res.data.msg || '服务器出错啦');
return Promise.reject(res.data.msg || '服务器出错啦');
} else {
return res.data;
}
}, (error) => {
console.log('进入错误!');
error.data = {};
if (error && error.response) {
switch (error.response.status) {
case 400:
error.data.msg = "错误请求";
ElMessage.error(error.data.msg);
break;
case 401:
error.data.msg = "未授权,请登录";
ElMessage.error(error.data.msg);
break;
case 403:
error.data.msg = "拒绝访问";
ElMessage.error(error.data.msg);
break;
case 404:
error.data.msg = "请求错误,未找到该资源";
ElMessage.error(error.data.msg);
break;
case 405:
error.data.msg = "请求方法未允许";
ElMessage.error(error.data.msg);
break;
case 408:
error.data.msg = "请求超时";
ElMessage.error(error.data.msg);
break;
case 500:
error.data.msg = "服务器端出错";
ElMessage.error(error.data.msg);
break;
case 501:
error.data.msg = "网络未实现";
ElMessage.error(error.data.msg);
break;
case 502:
error.data.msg = "网络错误";
ElMessage.error(error.data.msg);
break;
case 503:
error.data.msg = "服务不可用";
ElMessage.error(error.data.msg);
break;
case 504:
error.data.msg = "网络超时";
ElMessage.error(error.data.msg);
break;
case 505:
error.data.msg = "http版本不支持该请求";
ElMessage.error(error.data.msg);
break;
default:
error.data.msg = `连接错误${error.response.status}`;
ElMessage.error(error.data.msg);
}
} else {
error.data.msg = "连接到服务器失败";
ElMessage.error(error.data.msg)
}
return Promise.reject(error);
})
}
// GET方法
get<T = Result>(url: string, params?: object): Promise<T> {
return this.instance.get(url, { params });
}
// POST方法
post<T = Result>(url: string, data?: object): Promise<T> {
return this.instance.post(url, data);
}
// PUT方法
put<T = Result>(url: string, data?: object): Promise<T> {
return this.instance.put(url, data );
}
// DELETE方法
delete<T = Result>(url: string): Promise<T> {
return this.instance.delete(url);
}
}
export default new Http(config);
3、后端代码
3.1 GoodsCategoryController.java
java
@RestController
@RequestMapping("/api/goodsCategory")
public class GoodsCategoryController {
@Autowired
private GoodsCategoryService goodsCategoryService;
@Autowired
private GoodsCategorySonService goodsCategorySonService;
/**
* 获取查询列用于前端 u-picker 组件渲染值
* @return
*/
@GetMapping("/getSelectList")
public ResultVo getSelectList() {
List<Object> categoryList = goodsCategorySonService.getSelectLists();
return ResultUtils.success("查询成功!", categoryList);
}
}
3.2 GoodsCategoryService.java
java
package com.zhx.app.service.goods;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zhx.app.model.goods.GoodsCategorySon;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName : GoodsCategoryService
* @Description :
* @Author : zhx
* @Date: 2024-03-31 10:48
*/
public interface GoodsCategorySonService extends IService<GoodsCategorySon> {
List<Object> getSelectLists();
}
3.3 GoodsCategoryServiceImpl.java
java
package com.zhx.app.service.impl.goods;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhx.app.mapper.goods.GoodsCategoryMapper;
import com.zhx.app.mapper.goods.GoodsCategorySonMapper;
import com.zhx.app.model.goods.GoodsCategory;
import com.zhx.app.model.goods.GoodsCategorySon;
import com.zhx.app.service.goods.GoodsCategorySonService;
import io.micrometer.common.util.StringUtils;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* @ClassName : GoodsCategoryServiceImpl
* @Description :
* @Author : zhx
* @Date: 2024-03-31 10:49
*/
@Service
public class GoodsCategorySonServiceImpl extends ServiceImpl<GoodsCategorySonMapper, GoodsCategorySon> implements GoodsCategorySonService{
@Autowired
private GoodsCategoryMapper goodsCategoryMapper;
@Autowired
private GoodsCategorySonMapper goodsCategorySonMapper;
/**
* 格式化返回一级分类和二级分类列表
* @return
*/
@Override
public List<Object> getSelectLists() {
@Data
class SelectType {
private Long id;
private String name;
}
@Data
class SelectTypeSon {
private Long id;
private String name;
private Long categoryFatherId;
}
// 查询分类列表
// 构造查询
QueryWrapper<GoodsCategory> query = new QueryWrapper<>();
// 查询条件
query.lambda().orderByDesc(GoodsCategory::getCategoryId);
// 获取查询结果
List<GoodsCategory> list = goodsCategoryMapper.selectList(query);
// 构造查询
QueryWrapper<GoodsCategorySon> querySon = new QueryWrapper<>();
// 查询条件
querySon.lambda().orderByDesc(GoodsCategorySon::getOrderNum);
// 获取查询结果
List<GoodsCategorySon> listSon = goodsCategorySonMapper.selectList(querySon);
// 存储需要的类型
ArrayList<SelectType> selectList = new ArrayList<>();
ArrayList<SelectTypeSon> selectListSon = new ArrayList<>();
List<Object> category = new ArrayList<>();
// 构造需要的类型
Optional.ofNullable(list).orElse(new ArrayList<>())
.stream()
.forEach(x -> {
SelectType type = new SelectType();
type.setId(x.getCategoryId());
type.setName(x.getCategoryName());
selectList.add(type);
});
Optional.ofNullable(listSon).orElse(new ArrayList<>())
.stream()
.forEach(x -> {
SelectTypeSon type = new SelectTypeSon();
type.setId(x.getCategoryId());
type.setName(x.getCategoryName());
type.setCategoryFatherId(x.getCategoryFatherId());
selectListSon.add(type);
});
category.add(selectList);
category.add(selectListSon);
return category;
}
}