SpringBoot3 + Vue3 + Element-Plus + TS 实现动态二级菜单级联选择器

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;
    }
}
相关推荐
摇滚侠16 分钟前
Spring Boot 3零基础教程,WEB 开发 HttpMessageConverter @ResponseBody 注解实现内容协商源码分析 笔记33
java·spring boot·笔记
计算机毕业设计小帅33 分钟前
【2026计算机毕业设计】基于Springboot的校园电动车短租平台
spring boot·后端·课程设计
superlls1 小时前
(Spring)Spring Boot 中 @Valid 与全局异常处理器的联系详解
java·spring boot·后端
摇滚侠2 小时前
Spring Boot 3零基础教程,WEB 开发 整合 Thymeleaf 笔记36
java·spring boot·笔记
optimistic_chen3 小时前
【Java EE进阶 --- SpringBoot】Mybatis - plus 操作数据库
数据库·spring boot·笔记·java-ee·mybatis·mybatis-plus
来旺3 小时前
互联网大厂Java面试全解析及三轮问答专项
java·数据库·spring boot·安全·缓存·微服务·面试
摇滚侠3 小时前
Spring Boot 3零基础教程,yml文件中配置和类的属性绑定,笔记15
spring boot·redis·笔记
thginWalker3 小时前
使用Spring Boot构建消息通信层
spring boot
lang201509283 小时前
Spring Boot 外部化配置最佳实践指南
java·spring boot
摇滚侠4 小时前
Spring Boot 3零基础教程,WEB 开发 HTTP 缓存机制 笔记29
spring boot·笔记·缓存