SpringBoot3 + Vue3 + Element-Plus + TS 实现动态二级菜单选择器
- 1、效果展示
- 1.1 点击效果
- 1.2 选择效果
- 1.3 返回值
- 1.4 模拟后端返回数据
- 2、前端代码
- 2.1 UnusedList.vue
- 2.2 goodsType.ts
- 2.3 http.ts
- 3、后端代码
- 3.1 GoodsCategoryController.java
- 3.2 GoodsCategoryService.java
- 3.3 GoodsCategoryServiceImpl.java
1、效果展示
1.1 点击效果
1.2 选择效果
1.3 返回值
返回值为二级分类的 id
{categoryId: "21"
}
1.4 模拟后端返回数据
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、前端代码
vue_40">2.1 UnusedList.vue
<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
import http from "@/http/http.ts";
// 查询所有分类结构化的返回
export const getSelectListApi = () => {return http.get(`/api/goodsCategory/getSelectList`);
}
2.3 http.ts
/** @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
@RestController
@RequestMapping("/api/goodsCategory")
public class GoodsCategoryController {@Autowiredprivate GoodsCategoryService goodsCategoryService;@Autowiredprivate GoodsCategorySonService goodsCategorySonService;/*** 获取查询列用于前端 u-picker 组件渲染值* @return*/@GetMapping("/getSelectList")public ResultVo getSelectList() {List<Object> categoryList = goodsCategorySonService.getSelectLists();return ResultUtils.success("查询成功!", categoryList);}
}
3.2 GoodsCategoryService.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
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{@Autowiredprivate GoodsCategoryMapper goodsCategoryMapper;@Autowiredprivate GoodsCategorySonMapper goodsCategorySonMapper;/*** 格式化返回一级分类和二级分类列表* @return*/@Overridepublic List<Object> getSelectLists() {@Dataclass SelectType {private Long id;private String name;}@Dataclass 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;}
}