react+antd+CheckableTag实现Tag标签单选或多选功能

news2024/11/15 13:25:55

1、效果如下图

实现tag标签单选或多选功能

2、环境准备

1、react18

2、antd 4+

3、功能实现

原理: 封装一个受控组件,接受父组件的参数,数据发现变化后,回传给父组件

1、首先,引入CheckableTag组件和useEffect, useMemo, useState钩子:

import { Tag } from 'antd';
import { useEffect, useMemo, useState } from 'react';

const { CheckableTag } = Tag;

2、然后,定义一个状态变量来存储选中的tag:

const [tagsData, setTagsData] = useState<enumItem[]>();

3、组件可接收的props子属性 如下:

  •  selectedTagsValues: 父组件传入的标签配置枚举列表
  •  value: 已选中的值
  •  startIndex:距离左右节点位置,用于是否将左右节点滑动到可视局域
  •  onChange: 选中的值发生变化时回调

4、创建一个函数来处理tag的选中和取消选中事件:

  // handelChange,找到所点击项的索引,并把那一项的checked设置为true
  const handleChange = (tag: any, checked: boolean, mode?: string) => {
    if (mode !== 'multiple') {
      onChange?.(checked ? tag : null);
      return;
    }
    const changeData = (value || []).filter(
      (item: any) => item.value !== tag.value,
    );
    if (checked) {
      changeData.push(tag);
    }
    onChange?.(changeData);
  };

 5、最后,使用CheckableTag组件以及tagsData, value值的变化动态来渲染tag列表,并将选中状态和change事件绑定到对应的属性上:

  //遍历
  const dom = useMemo(() => {
    return (
      <div id={uniqueKey} className={clsx(['flex'])}>
        {(tagsData || []).map((tag: any, index: number) => {
          const isHasSelectedTag = isSelectedTag(tag?.value, value);
          return (
            // eslint-disable-next-line react/jsx-key
            <div className={clsx(['self-check-tag'])} key={index}>
              <CheckableTag
                key={tag.value}
                checked={tag.checked || isHasSelectedTag}
                onClick={(e: any) => {
                  scrollIntoViewHandle(
                    e.target?.parentElement?.parentElement?.parentElement
                      ?.childNodes,
                    index,
                    tagsData?.length || 0,
                  );
                }}
                onChange={(checked) => {
                  handleChange(tag, checked, mode);
                }}
              >
                <div className={clsx([mode === 'multiple' ? 'cur' : ''])}>
                  {tag.label}
                  {tag.checked || isHasSelectedTag ? <i></i> : ''}
                </div>
              </CheckableTag>
            </div>
          );
        })}
      </div>
    );
  }, [tagsData, value]);

6、完整代码如下:

/**
 * 公共组件:标签组件
 */
import { Tag } from 'antd';
import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
import './index.less';

const { CheckableTag } = Tag;

type enumItem = {
  label: string;
  value: string;
};
// 距离左右节点的位置的默认值,决定开始、结束节点是否滚到可视区域
const START_INDEX = 3;

/**
 * 标签属性配置
 * selectedTagsValues: 可选中的标签配置选项
 * uniqueKey:组件唯一标识key
 * value: 已选中的值
 * startIndex:距离左右节点位置,用于是否将左右节点滑动到可视局域
 * onChange: 选中的值发生变化时回调
 */
interface SelectTagProps {
  selectedTagsValues: enumItem[];
  uniqueKey?: string;
  value?: any;
  startIndex?: number;
  mode?: string;
  onChange?: (values: any) => void;
}

const SelectTag = (props: SelectTagProps) => {
  const { selectedTagsValues, uniqueKey, value, startIndex, mode, onChange } =
    props;
  const [tagsData, setTagsData] = useState<enumItem[]>();

  // 点击tag 跳到对应的可视区域
  const scrollIntoViewHandle = (node: any, index: number, tagLen: number) => {
    const scrollIndex =
      startIndex || Math.min(START_INDEX, Math.floor(tagLen / 2));
    if (tagLen > 0 && index < scrollIndex) {
      node?.[0]?.scrollIntoView({
        behavior: 'smooth',
        block: 'start',
        inline: 'start',
      });
      return;
    }
    if (tagLen > 0 && tagLen - index <= scrollIndex) {
      node?.[tagLen - 1]?.scrollIntoView({
        behavior: 'smooth',
        block: 'start',
        inline: 'start',
      });
      return;
    }
    node?.[index]?.scrollIntoView({
      behavior: 'smooth',
      block: 'center',
      inline: 'center',
    });
  };

  useEffect(() => {
    setTagsData(selectedTagsValues);
  }, [selectedTagsValues]);

  useEffect(() => {
    // 若枚举过长,可考虑传入一个uniqueKey,自动调到指定位置
    if (uniqueKey && value) {
      const index: number = (tagsData || []).findIndex(
        (item: any) => item.value && item?.value === value?.value,
      );
      if (index > -1 && document.getElementById(uniqueKey)) {
        setTimeout(() => {
          scrollIntoViewHandle(
            document.getElementById(uniqueKey)?.childNodes,
            index,
            tagsData?.length || 0,
          );
        }, 100);
      }
    }
  }, [value]);

  // handelChange,找到所点击项的索引,并把那一项的checked设置为true
  const handleChange = (tag: any, checked: boolean, mode?: string) => {
    if (mode !== 'multiple') {
      onChange?.(checked ? tag : null);
      return;
    }
    const changeData = (value || []).filter(
      (item: any) => item.value !== tag.value,
    );
    if (checked) {
      changeData.push(tag);
    }
    onChange?.(changeData);
  };

  // tag是否选中
  const isSelectedTag = (tagValue: string, value: any) => {
    if (mode === 'multiple') {
      if (Array.isArray(value) && value.length) {
        const findIndex = value.findIndex((item) => item?.value === tagValue);
        return findIndex > -1;
      }
      return false;
    }
    return tagValue === value?.value;
  };

  //遍历
  const dom = useMemo(() => {
    return (
      <div id={uniqueKey} className={clsx(['flex'])}>
        {(tagsData || []).map((tag: any, index: number) => {
          const isHasSelectedTag = isSelectedTag(tag?.value, value);
          return (
            // eslint-disable-next-line react/jsx-key
            <div className={clsx(['self-check-tag'])} key={index}>
              <CheckableTag
                key={tag.value}
                checked={tag.checked || isHasSelectedTag}
                onClick={(e: any) => {
                  scrollIntoViewHandle(
                    e.target?.parentElement?.parentElement?.parentElement
                      ?.childNodes,
                    index,
                    tagsData?.length || 0,
                  );
                }}
                onChange={(checked) => {
                  handleChange(tag, checked, mode);
                }}
              >
                <div className={clsx([mode === 'multiple' ? 'cur' : ''])}>
                  {tag.label}
                  {tag.checked || isHasSelectedTag ? <i></i> : ''}
                </div>
              </CheckableTag>
            </div>
          );
        })}
      </div>
    );
  }, [tagsData, value]);

  return <>{dom}</>;
};

export default SelectTag;

样式文件:

.self-check-tag {
  display: flex;

  .ant-tag {
    display: inline-block;
    height: 32px;
    font-size: 14px;
    font-family: 'Microsoft YaHei';
    color: #fff;
    line-height: 32px;
    border-radius: 3px;
    padding-left: 10px;
    padding-right: 10px;
  }

  div.cur {
    position: relative;
    // padding: 0 12px;
  }

  div.cur > i {
    display: block;
    position: absolute;
    border-bottom: 16px solid #1890ff;
    border-left: 16px solid transparent;
    width: 0;
    height: 0;
    bottom: 1px;
    right: -8px;
    content: '';
  }

  div.cur > i::before {
    content: '';
    position: absolute;
    top: -1px;
    right: -2px;
    border: 16px solid #1890ff;
    border-top-color: transparent;
    border-left-color: transparent;
  }

  div.cur > i::after {
    content: '';
    width: 6px;
    height: 10px;
    position: absolute;
    right: 0;
    top: 3px;
    border: 1px solid #fff;
    border-top-color: transparent;
    border-left-color: transparent;
    transform: rotate(40deg);
  }

  .ant-tag-checkable-checked {
    color: #2eb3ff;
    background-color: rgba(46, 179, 255, 10%);
  }

  .ant-tag-checkable:hover {
    color: #2eb3ff;
    background-color: rgba(46, 179, 255, 10%);
  }
}

组件调用:

const selectedTagsValues: any[] = [
  { label: '特大型', value: '1' },
  { label: '大型2级', value: '2' },
  { label: '大型3级', value: '3' },
  { label: '中型4级', value: '4' },
  { label: '中型5级', value: '5' },
  { label: '小型', value: '6' },
]

<SelectTag selectedTagsValues={selectedTagsValues} />

下一节将分享多层级的标签选中功能,同时支持多选和单选功能

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

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

相关文章

echarts 一条折线图上显示不同颜色

文档树懒学堂&#xff1a;ECharts visualMap 代码实例及对应注释 - 树懒学堂 封装的echarts 组件代码&#xff1a; <template> <div :style"{ height: 100% }"> <div class"foldLine" ref"foldLine" :style"{ width: width…

ValueError: PEFT backend is required for this method.

根据异常栈发现USE_PEFT_BACKEND是False导致的 if not USE_PEFT_BACKEND:raise ValueError("PEFT backend is required for this method.")找到定义此变量文件&#xff0c;PEFT >0.6 and transformers > 4.34.0 解决方法 升级包 pip install -U PEFT tra…

2.6:冒泡、简选、直插、快排,递归,宏

1.冒泡排序、简单选择排序、直接插入排序、快速排序(升序) 程序代码&#xff1a; 1 #include<stdio.h>2 #include<string.h>3 #include<stdlib.h>4 void Bubble(int arr[],int len);5 void simple_sort(int arr[],int len);6 void insert_sort(int arr[],in…

pwn旅行之[WUSTCTF 2020]getshell2(一些小知识)

题目分析1 首先打开这个题目的链接的时候&#xff0c;看到了ret2syscall&#xff0c;以为是一个纯正的syscall的题&#xff0c;结果&#xff0c;做的时候发现这个题的危险函数限制的字符串个数不足以写入syscall需要的所有地址&#xff0c;所以&#xff0c;这里参考dalao们的方…

成都爱尔林江院长讲解RGP,一种透氧且度数“上限极高”的隐形眼镜

想戴隐形眼镜&#xff0c;脱离框架的束缚。 想戴可又不知道该怎么选。 能夜晚戴镜白天摘镜的角膜塑形镜&#xff0c;近视超过600度又戴不了。 软性隐形眼镜无论透明的还是美瞳&#xff0c;大多透氧率低对眼睛也不太好。 RGP&#xff0c;一种硬性透氧性角膜接触镜&#xff0…

甲骨文吴承杨:2024年才刚开始,生成式AI已在重塑行业 | 探路2024

ITValue 到2026年&#xff0c;将有超过80%的企业使用生成式AI的API或模型&#xff0c;或在生产环境中部署支持生成式AI应用。 作者&#xff5c;杨丽 首发&#xff5c;钛媒体 ITValue “企业需要的是能够改善业务成果的生成式AI功能和用例。除此之外&#xff0c;企业还需要一个可…

探索Web API SpeechSynthesis:给你的网页增添声音

Web API SpeechSynthesis是一项强大的浏览器功能&#xff0c;它允许开发者将文本转换为语音&#xff0c;并通过浏览器播放出来。本文将深入探讨SpeechSynthesis的控制接口&#xff0c;包括其功能、用法和一个完整的JavaScript示例。 参考资料&#xff1a;SpeechSynthesis - Web…

安卓学习笔记之八:本地化的简单例子(kotlin版本)

本地化及多语言支持&#xff0c;是目前手机软件必须面对的问题&#xff0c;这里用一个简单的例子来说明在Android Studio下如何实现。 创建一个Empty Views Activity项目&#xff0c;语言选择Kotlin 实现一个简单的功能&#xff0c;一条欢迎&#xff0c;一个按钮&#xff0c;…

博客|基于Springboot的个人博客系统设计与实现(源码+数据库+文档)

个人博客系统目录 目录 基于Springboot的个人博客系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能实现 &#xff08;1&#xff09;用户管理 &#xff08;2&#xff09;文章分类管理 &#xff08;3&#xff09;公告信息管理 &#xff08;4&#…

【数据分享】1929-2023年全球站点的逐日降水量数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;说到常用的降水数据&#xff0c;最详细的降水数据是具体到气象监测站点的降水数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全…

Vue3大事件项目(ing)

文章目录 核心内容1.大事件项目介绍2.大事件项目创建3.Eslint配置代码风格4.配置代码检查工作流问题: pnpm lint是全量检查,耗时问题,历史问题 5.目录调整6.vue-router4 路由代码解析7.引入 Element Plus 组件库8.Pinia 构建仓库 和 持久化9.Pinia 仓库统一管理 核心内容 Vue3…

2024年信息管理与工业制造与自动化国际学术会议(ICIMIMA2024)

2024年信息管理与工业制造与自动化国际学术会议(ICIMIMA2024) 会议简介 2024年信息管理与工业制造及自动化国际学术会议&#xff08;ICIMIMA2024&#xff09;将在中国三亚举行。会议旨在为信息管理和工业工程领域的专家、学者、工程师和技术人员提供一个平台&#xff0c;分享…

【数据结构】链表OJ面试题3(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 记录每天的刷题&#xff0c;继续坚持&#xff01; 2.OJ题目训练 9. 给定一个链表&#xff0c;判断链表中是否有环。 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成…

python flask 魔术方法

魔术方法作用_init_对象的初始化方法_class_返回对象所属的类_module_返回类所在的模块_mro_返回类的调用顺序&#xff0c;可以找到其父类&#xff08;用于找父类&#xff09;_base_获取类的直接父类&#xff08;用于找父类&#xff09;_bases_获取父类的元组&#xff0c;按它们…

开启一个服务,将服务器指定的文件读取,传播到网上其他终端

from flask import Flask, render_template_string app Flask(__name__)app.route(/get-data) def get_data():# 读取data.txt文件的内容with open(r./2024/2/4/data.txt, r) as file:data file.read()print(data)# 返回数据的HTML表示return render_template_string(<div…

成功解决:AssertionError: Torch not compiled with CUDA enabled

在运行pycharm项目的时候&#xff0c;出现了以上的报错&#xff0c;主要可以归结于以下两个个方面&#xff1a; 1、没有安装GPU版本的pytorch&#xff0c;只是使用清华的镜像地址下载了CPU版本的pytorch 2、安装的CUDA和安装的pytorch的版本不相互对应 我使用 pip list 来…

【2024.2.5练习】砍竹子(25分)

题目描述 题目分析 考虑题目是否满足贪心。每次施展魔法会使一段连续的竹子高度变为一半左右的平方根。根据样例&#xff0c;似乎每次让最高的竹子变短就能得到最优解。 假设魔法一次只能对一根竹子使用&#xff0c;永远不出现连续相同高度的竹子&#xff0c;那么显然无论使用…

逆向工程:揭开科技神秘面纱的艺术

在当今这个科技飞速发展的时代&#xff0c;我们每天都在与各种电子产品、软件应用打交道。然而&#xff0c;你是否想过&#xff0c;这些看似复杂的高科技产品是如何被创造出来的&#xff1f;今天&#xff0c;我们就来探讨一下逆向工程这一神秘而又令人着迷的领域。 一、什么是…

Unity 接口、抽象类、具体类对象的配合使用案例

文章目录 示例1&#xff1a;接口&#xff08;Interface&#xff09;示例2&#xff1a;抽象类&#xff08;Abstract Class&#xff09;示例3&#xff1a;结合使用接口与抽象类示例4&#xff1a;多接口实现示例5&#xff1a;抽象类与接口结合 在Unity中使用C#编程时&#xff0c;接…

SSL协议是什么?关于SSL和TLS的常见问题解答

SSL&#xff08;安全套接字层&#xff09;及其后继者TLS&#xff08;传输层安全&#xff09;是用于在联网计算机之间建立经过身份验证和加密的链接的协议。尽管SSL协议在 1999年已经随着TLS 1.0的发布而被弃用&#xff0c;但我们仍将这些相关技术称为“SSL”或“SSL/TLS”。那么…