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

news2024/9/20 2:45:07

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、前端代码

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 {
    @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

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{
    @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;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1611994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网抓取Windows密码明文与hashdump思考题笔记整理

目录 思考题 第一题 第二题 第三题 第四题 第五题 思考题 1.windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff0c;密文存在哪个文件下&#xff0c;该文件是否可以打开&#xff0c;并且查看到密文 2.我们通过hashdump 抓取出 所有用户的密文&#xff0c;分为…

Mysql学习2

目录 一.数据库&#xff1a; 1.创建数据库&#xff1a; 2.查看数据库&#xff1a; 3.备份恢复数据库&#xff1a; 二.表 1.创建表指令&#xff1a; 2.MySQL常用数据类型&#xff1a; 3.删除与修改表&#xff08;重点&#xff09;&#xff1a; 4.数据库CRUD语句&#xf…

简述PDF原理和实践

Hello&#xff0c;我是小恒不会java。 由于最近有输出PDF报表的项目需求&#xff0c;所以复习一下PDF到底是什么&#xff0c;该如何产生&#xff0c;如何应用至项目中。 更多参见Adobe官方文档&#xff08;https://www.adobe.com/cn/&#xff09; PDF原理 PDF&#xff08;Port…

Linux内核之文件系统访问:目录项、inode、物理磁盘访问关系(五十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

(数据结构代码,总结,自我思考)=> { return 个人学习笔记; } 【To be continued~】

俗话说 “学而不思则罔”&#xff0c;是时候复习和整理一下自己先前的学习历程了&#xff01; Chapter-One 《BinarySearch》 public static int binarySearch (int[] a, int target) {int i 0, j a.length - 1;while (i < j) {int m (i j) >>> 1; // 求中位…

小红书电商运营实战课,从0打造全程实操(65节视频课)

课程内容&#xff1a; 1.小红书的电商介绍 .mp4 2.小红书的开店流程,mp4 3.小红书店铺基础设置介绍 ,mp4 4.小红书店铺产品上架流程 .mp4 5.客服的聊天过程和子账号建立 .mp4 6.店铺营销工具使用和后台活动参加 .mp4 7.小红书产品上架以及拍单教程,mp4 8.小红书如何选品…

javaWeb项目-智慧餐厅点餐管理系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、JavaScript Java…

UltraScale+的40G/50G Ethernet Subsystem IP核使用

文章目录 前言一、设计框图二、模块说明三、上板3.1、发送端3.1、接收端 四、总结 前言 上文介绍了10G/25G Ethernet Subsystem IP核使用&#xff0c;本文将在此基础上介绍40G/50G Ethernet Subsystem IP核的使用&#xff0c;总体区别不大。 一、设计框图 由于40G以太网需要…

Jetson nx 外接OLED屏幕

40 针 GPIO 引脚 GPIO引脚可以用作输入或输出端口&#xff0c;它们提供了一个数字电平以使用户在外界设备上进行控制或读取。Jetson TX2 NX共有198个GPIO引脚&#xff0c;分为三个不同的管脚组&#xff1a;J1、J21和J22。每个管脚组都具有数字输入/输出和PWM功能。 以下是 TX2…

获取AngusTester应用免费许可

第一步、进入晓蚕云官网私有化部署&#xff0c;滑动到底部下载与安装&#xff0c;点击获取许可。 第二步、在获取许可申请页面填写申请信息。 注意&#xff1a;MAC地址为您安装应用服务器对应MAC地址&#xff0c;MAC地址错误会导致安装失败。 在常见的操作系统中&#xff0c;查…

Error in render: TypeError: Cannot read properties of undefined (reading‘‘)

报错内容 报错解释&#xff1a;这个错误在渲染过程中尝试读取一个未定义&#xff08;undefined&#xff09;对象的某个属性时发生了TypeError。具体来说&#xff0c;是尝试读取一个值为undefined的对象的某个属性&#xff0c;但该属性不存在&#xff0c;因此无法读取。解决过程…

【面试经典 150 | 二叉搜索树】验证二叉搜索树

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;中序遍历方法二&#xff1a;递归 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及…

国产信创办公软件(流版式软件)厂家汇总以及国产信创外设汇总

国产信创办公软件&#xff08;流版式软件&#xff09;厂家汇总以及国产信创外设汇总。 国产信创办公软件&#xff08;流版式软件&#xff09;厂家汇总 在信创背景下&#xff0c;国内流版式软件的发展呈现出蓬勃的态势。信创&#xff0c;即信息技术应用创新产业&#xff0c;旨在…

【C++】日期类Date(详解)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 日期类 日期类实现地图 获取某年某月的天数&#xff1a;GetMonthDay 检查日期合法&#xff0c;构造函数&#xff0c;拷贝构造函数&#xff0c;赋值运算符重载及析构函数…

【Qt】探索Qt框架:跨平台GUI开发的利器

文章目录 1. Qt框架概述1.1. Qt框架的优点1.2. Qt框架支持的系统1.3. Qt开发环境 2. 搭建 Qt 开发环境2.1. Qt SDK 的下载和安装2.2. 新建项目: 3. Qt 框架内容简介总结 在当今软件开发领域&#xff0c;跨平台性和用户界面的友好性是至关重要的。而Qt框架作为一款跨平台的C图形…

<计算机网络自顶向下> TCP拥塞

目录 TCP拥塞控制机制 TCP拥塞感知 TCP速率控制方法 TCP拥塞控制和流量控制的联合动作 TCP拥塞控制策略 TCP吞吐量 TCP公平性 TCP拥塞控制机制 端到端的拥塞控制机制 路由器不向主机提供有关拥塞的反馈信息 路由器负担较轻 符合网络核心简单的TCP/IP架构原则 端系统根据自…

【github主页】优化简历

【github主页】优化简历 写在最前面一、新建秘密仓库二、插件卡片配置1、仓库状态统计2、Most used languages&#xff08;GitHub 常用语言统计&#xff09;使用细则 3、Visitor Badge&#xff08;GitHub 访客徽章&#xff09;4、社交统计5、打字特效6、省略展示小猫 &#x1f…

浅谈命理学的男女婚姻篇

对于中国人来说&#xff0c;八字预测已成为生活中不可缺少的组成部分&#xff0c;不懂八字预测&#xff0c;就不会真正了解中国的传统文化。八字预测经历几千年的风风雨雨&#xff0c;是一种古老的中国命理学&#xff0c;通过生辰八字的天干地支的组合&#xff0c;可以推测一个…

[阅读笔记21][RA-CM3]Retrieval-Augmented Multimodal Language Modeling

这篇论文是meta联合斯坦福在23年4月发表的论文&#xff0c;提出了一个使用外部知识检索增强的多模态模型。 这篇模型提出的RA-CM3模型是第一个能够检索并生成图像文本的多模态模型&#xff0c;在图像文本生成任务上优于现有的多模态模型&#xff0c;同时使用更少的训练量。 RA-…

模型 框架效应

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。部分真相不等于真相。 1 框架效应的应用 1.1 框架效应在营销策略上的应用 亚洲航空公司面临的挑战是如何在竞争激烈的航空市场中吸引更多的顾客&#xff0c;并提高机票的预订率。这家低成本航空公司…