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

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.mfbz.cn/a/375730.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

动态规划01 三步问题[C++]

​​​​​​ 图源&#xff1a;文心一言 上机题目练习整理&#xff0c;本篇作为动态规划的代码&#xff0c;因为做题入门很少找到带图的讲解&#xff08;难道是因为太简单&#xff0c;所以没有人嘛&#xff09;&#xff0c;所以干脆自己写一份&#xff0c;供小伙伴们参考~&am…

CSS的动画

CSS的动画 在本节&#xff0c;我们将学习keyframes动画。 1. 动画的基本使用 1. 定义动画 定义动画有两种写法&#xff1a; 简单定义方式 keyframes 动画名 {/* from代表初始状态 */from {/*property1:value1*/transform: translate(0%);}/* to代表结束状态 */to {transfor…

Win32 SDK Gui编程系列之--弹出式菜单

1.弹出式菜单 例如,在命令提示窗口中点击鼠标右键,会出现如下图所示的弹出菜单(下拉菜单)。 这种弹出式菜单的实现很简单。不创建菜单栏,用CreatePopupMenu函数创建的菜单是最顶端的菜单就可以了。 菜单的显示使用TrackPopupMenu函数进行。 例如,点击鼠标右键显示弹出…

JAVA装饰器模式详解

装饰器模式 1 装饰器模式介绍 装饰模式(decorator pattern) 的原始定义是&#xff1a;动态的给一个对象添加一些额外的职责. 就扩展功能而言,装饰器模式提供了一种比使用子类更加灵活的替代方案. 假设现在有有一块蛋糕,如果只有涂上奶油那这个蛋糕就是普通的奶油蛋糕, 这时如…

[职场] 智能材料与结构专业的就业前景 #经验分享#学习方法

智能材料与结构专业的就业前景 智能材料与结构专业是面向国家智能制造强国战略&#xff0c;面向地方经济新旧动能转换需求&#xff0c;学习智能材料与结构的基础理论及基本知识&#xff0c;接受智能材料制备、组织分析、性能测试、智能材料系统集成技能的基本训练&#xff0c;…

蓝桥杯省赛无忧 课件127 线段树维护哈希

01 问题引入 02 算法思路 03 代码实现 04 例题讲解

Linux中共享内存(mmap函数的使用)

内存映射的基本使用 内存映射 概念&#xff1a; 使一个磁盘文件与内存中的一个缓冲区相映射&#xff0c;进程可以像访问普通内存一样对文件进行访问&#xff0c;不必再调用read,write。 mmap()的优点&#xff1a; 实现了用户空间和内核空间的高效交互方式 优化前&#xff1a;优…

时序数据库Influxdb查询多个字段_field同一时间的值,组成一条数据

Influxdb将表格数据多个字段_field从垂直列布局聚合成水平布局行字段。 问题 1、Influxdb 是一种时间序列数据库&#xff0c;在我的项目中主要用来存储换热站的测点数据的。换热站有非常多的测点&#xff0c;我们用Flux 语法去查询测点数据&#xff0c;返回的数据结构是每个测…

【MySQL进阶之路】MySQL生产环境部署该如何选择机器配置?

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

酷开科技,打造非凡的生活体验

酷开科技&#xff0c;作为一家专注于智能电视操作系统研发及智能电视运营增值服务的高科技企业&#xff0c;始终致力于为消费者提供非凡的生活体验。通过不断创新的技术和产品&#xff0c;酷开科技为消费者们带来了便捷、舒适、个性化的智能生活。 首先&#xff0c;酷开科技在智…

阿里云游戏服务器收费价格表,一年和1个月报价

阿里云游戏服务器租用价格表&#xff1a;4核16G服务器26元1个月、146元半年&#xff0c;游戏专业服务器8核32G配置90元一个月、271元3个月&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云游戏专用服务器详细配置和精准报价&#xff1a; 阿里云游戏服务器租用价格表 阿…

【 buuctf-数据包中的线索】

这题比较简单&#xff0c;只要能想到base64 转图片 复制此处密文&#xff0c;发现 base64 正常解码解不出来&#xff0c;就要想到可能是图片 需要注意&#xff0c;/9j是jpg文件头的 base64 编码 BUUCTF的wireshark流量分析题目writeup汇总_ctf wireshark练习题-CSDN博客&#…

蓝桥杯刷题day07——斐波那契与7

1、题目描述 斐波那契数列的递推公式为:FnFn-1Fn-2, 其中F1F21. 请问, 斐波那契数列的第 1 至 202202011200 项&#xff08;含&#xff09;中, 有多少项的个位 是 7 。 答案提交 这是一道结果填空的题, 你只需要算出结果后提交即可。本题的结果为一 个整数, 在提交答案时只填…

Maven构建OSGI+HttpServer应用

Maven构建OSGIHttpServer应用 官网&#xff08;https://eclipse.dev/equinox/server/http_in_equinox.php&#xff09;介绍有两种方式&#xff1a; 一种是基于”org.eclipse.equinox.http”包的轻量级实现&#xff0c;另一种是基于”org.eclipse.equinox.http.jetty”包&#…

2024最新CleanMyMac X优化Mac电脑系统体验分享

使用Mac电脑的用户都希望它们能够保持最佳性能。但有时&#xff0c;你可能会发现你的Mac运行变慢或响应迟缓。这可能是由于几个原因造成的&#xff0c;包括硬盘空间不足、系统缓存堆积、以及过多的启动项等。解决了这些问题&#xff0c;你可以显著提升你的Mac性能。本文将探讨导…

工程示例(LED、流水灯、蜂鸣器)

LED闪烁 #include "stm32f10x.h" // Device header #include "Delay.h"int main(void) {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitSructure;GPIO_InitSructure.GPIO_Mode GPIO_Mode_Out_PP;GPIO…

SpringBoot+随机盐值+双重MD5实现加密登录

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、salt…

遗失的源代码之回归之路的探索与实践

背景 最近比较突然被安排接手一个项目,该项目的情况如下 原生和RN结合的混合开发模式组件化开发,有很多基础组件以及业务组件但是在梳理项目依赖时发现了个别组件源码不全的情况,于是写了个cli用于对比两个版本产物文件,生成差异结果以便于快速进行源码找回恢复。 结果如下…

Python tkinter (16) —— Progressbar

本文主要介绍Python tkinter 进度条Progressbar应用及示例。 目录 系列文章 进度条Progressbar 基本概念 参数&#xff1a; mode参数 基本应用 动画设计 引入time 具体实现 start/step/stop step(amount)&#xff1a; start(interval)&#xff1a; stop()&#xff…

QT 范例阅读:系统托盘 The System Tray Icon example

main.cpp QApplication app(argc, argv);//判断系统是否支持 系统托盘功能if (!QSystemTrayIcon::isSystemTrayAvailable()) {QMessageBox::critical(0, QObject::tr("Systray"),QObject::tr("I couldnt detect any system tray ""on this system.&qu…
最新文章