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;
    }
}
相关推荐
【D'accumulation】1 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
2401_854391081 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
长路 ㅤ   1 小时前
cherry-markdown开源markdown组件详细使用教程
vue·markdown组件
wxin_VXbishe1 小时前
springboot合肥师范学院实习实训管理系统-计算机毕业设计源码31290
java·spring boot·python·spring·servlet·django·php
OEC小胖胖2 小时前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617622 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端
quokka562 小时前
Springboot 整合 logback 日志框架
java·spring boot·logback
计算机学姐2 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
2401_854391083 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
杨荧4 小时前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源