React动态添加标签组件

news2024/11/20 14:39:44

背景

在前端开发的过程中,一些表单的输入经常需要输入多个内容,如果采用一个输入框+逗号分隔的方式,展示起来不是很清晰,一般需要采用标签的方式

需求

59b47a790a3037dee50a566d04f1e23b.png

7edd8e6b322531d15b0f1a28fbe3113a.png

  1. 可以指定空状态时的标题

  2. 设置标签颜色

  3. 每个标签的最大长度(字符数)

  4. 接口传递的时候的分隔标记(是用逗号,还是其他)

  5. 直接处理表单,不需要二次处理

所以需要传入以下内容给该组件

  • title:标题

  • separator:分隔标记

  • maxLength:最大长度

  • color:颜色

  • form,name:处理的表单和对应的字段

const { title = '新增一个', separator = ',', maxLength = 40, color = 'orange', form, name } = props;

TagInput.propTypes = {
  title: PropTypes.string, // 新增一个tag的标题
  separator: PropTypes.string, // 分隔符
  maxLength: PropTypes.number, // tag最大长度
  color: PropTypes.string, // tag颜色
  form: PropTypes.object, // form
  key: PropTypes.string, // form的key
};

代码编写

是否显示输入框

首先需要有一个虚线框的标签

<Tag style={{ background: '#fff', borderStyle: 'dashed' }}>
    <PlusOutlined /> {title}
</Tag>

点击后出现文本输入框

<Input type="text" size="small" style={{ width: 78 }} />

并且锚定这个输入框(出现输入光标)

所以需要有一个状态记录是否显示输入框

const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框

所以上述代码变为:

const saveInputRef = useRef();

useEffect(() => {
  if (inputVisible) saveInputRef.current.focus();
}, [inputVisible]);

{inputVisible && (
  <Input ref={saveInputRef} type="text" size="small" style={{ width: 78 }} />
)}
{!inputVisible && (
  <Tag onClick={() => setInputVisible(true)} style={{ background: '#fff', borderStyle: 'dashed' }}>
    <PlusOutlined /> {title}
  </Tag>
)}

useEffect监听输入框是否出现,如果出现,则锚定「saveInputRef.current.focus()」

添加一个标签

为了记录输入框的内容定义一个新的变量

const [inputValue, setInputValue] = useState(''); // 输入框的值

<Input ref={saveInputRef} type="text" size="small" style={{ width: 78 }} value={inputValue} onChange={(e) => setInputValue(e.target.value)} />

每次输入内容都会修改inputValue的值

因为有多个标签,先定义一个变量来记录我们已经添加的标签

const [tags, setTags] = useState([]); // 待分隔列表

当鼠标在输入框外部点击或者敲击回车的时候,都需要添加一个标签

所以需要给输入框添加onBlur和onPressEnter方法

<Input
  ref={saveInputRef}
  type="text"
  size="small"
  style={{ width: 78 }}
  value={inputValue}
  onChange={(e) => setInputValue(e.target.value)}
  onBlur={handleInputConfirm}
  onPressEnter={handleInputConfirm}
/>

编写添加标签的方法:handleInputConfirm

  • 拿到之前的标签+本次输入的,一起放到tags变量中

  • 给表单设置一下这个值(用分隔标记拼接起来)

  • 隐藏输入框

  • 清空输入框

/*
 * 新增一个tag
 * */
const handleInputConfirm = () => {
  if (inputValue && tags.indexOf(inputValue) === -1) {
    const newTags = [...tags, inputValue];
    setTags(newTags);
    form.setFieldsValue({ [name]: newTags?.join(separator) });
  } else {
    message.error('请正确输入');
  }
  setInputVisible(false);
  setInputValue('');
};

展示标签

在上述步骤之后,tags中已经添加了我们的标签了,将它展示出来

  • 判断字符串长度,如果大于我们配置的最大长度则裁剪,没有则全部展示

  • 超长的标签增加一个气泡提示,鼠标移动上去后可以看到全部内容

{tags.map((tag) => {
  const isLongTag = tag.length > maxLength;
  const tagElem = (
    <Tag key={tag} color={color}>
      {isLongTag ? `${tag.slice(0, maxLength)}...` : tag}
    </Tag>
  );
  return isLongTag ? (
    <Tooltip title={tag} key={tag}>
      {tagElem}
    </Tooltip>
  ) : (
    tagElem
  );
})}

删除标签

给Tag设置closable和onClose方法

const tagElem = (
  <Tag key={tag} closable onClose={() => handleClose(tag)} color={color}>
    {isLongTag ? `${tag.slice(0, 20)}...` : tag}
  </Tag>
);

handleClose方法:

  • 过滤tags中不需要的tag并更新

  • 重新给表单对应的键值对赋值

/*
 * 删除某个tag
 * */
const handleClose = (removedTag) => {
  const updatedTags = tags.filter((tag) => tag !== removedTag);
  setTags(updatedTags);
  form.setFieldsValue({ [name]: updatedTags?.join(separator) });
};

编辑状态

当我们处于编辑状态的时候,打开表单后,它原本就有内容了

监听一下表单的内容,如果存在,则使用分隔标记分隔后塞入tags中

useEffect(() => {
    if (form.getFieldValue(name)) setTags(form.getFieldValue(name).split(separator));
  }, [form.getFieldValue(name)]);

Antd4.x完整代码

折叠源码

import React, { memo, useEffect, useRef, useState } from 'react';
import { Input, message, Tag, Tooltip } from 'antd';
import PropTypes from 'prop-types';
import { PlusOutlined } from '@ant-design/icons';

/*
 * tag形式分隔
 * */
const TagInput = memo((props) => {
  const [tags, setTags] = useState([]); // 待分隔列表
  const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框
  const [inputValue, setInputValue] = useState(''); // 输入框的值
  const { title = '新增一个', separator = ',', maxLength = 40, color = 'orange', form, name } = props;
  const saveInputRef = useRef();

  useEffect(() => {
    if (inputVisible) saveInputRef.current.focus();
  }, [inputVisible]);

  useEffect(() => {
    if (form.getFieldValue(name)) setTags(form.getFieldValue(name).split(separator));
  }, [form.getFieldValue(name)]);

  /*
   * 删除某个tag
   * */
  const handleClose = (removedTag) => {
    const updatedTags = tags.filter((tag) => tag !== removedTag);
    setTags(updatedTags);
    form.setFieldsValue({ [name]: updatedTags?.join(separator) });
  };

  /*
   * 新增一个tag
   * */
  const handleInputConfirm = () => {
    if (inputValue && tags.indexOf(inputValue) === -1) {
      const newTags = [...tags, inputValue];
      setTags(newTags);
      form.setFieldsValue({ [name]: newTags?.join(separator) });
    } else {
      message.error('请正确输入');
    }
    setInputVisible(false);
    setInputValue('');
  };

  return (
    <>
      {tags.map((tag) => {
        const isLongTag = tag.length > maxLength;
        const tagElem = (
          <Tag key={tag} closable onClose={() => handleClose(tag)} color={color}>
            {isLongTag ? `${tag.slice(0, 20)}...` : tag}
          </Tag>
        );
        return isLongTag ? (
          <Tooltip title={tag} key={tag}>
            {tagElem}
          </Tooltip>
        ) : (
          tagElem
        );
      })}
      {inputVisible && (
        <Input
          ref={saveInputRef}
          type="text"
          size="small"
          style={{ width: 78 }}
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onBlur={handleInputConfirm}
          onPressEnter={handleInputConfirm}
        />
      )}
      {!inputVisible && (
        <Tag onClick={() => setInputVisible(true)} style={{ background: '#fff', borderStyle: 'dashed' }}>
          <PlusOutlined /> {title}
        </Tag>
      )}
    </>
  );
});

TagInput.propTypes = {
  title: PropTypes.string, // 新增一个tag的标题
  separator: PropTypes.string, // 分隔符
  maxLength: PropTypes.number, // tag最大长度
  color: PropTypes.string, // tag颜色
  form: PropTypes.object, // form
  key: PropTypes.string, // form的key
};

export default TagInput;

Antd3.x完整代码

antd3.x中部分组件的用法不一样,需要修改一下

折叠源码

import React, { useEffect, useRef, useState } from 'react';
import { Icon, Input, message, Tag, Tooltip } from 'antd';
import PropTypes from 'prop-types';

/*
 * tag形式分隔
 * */
const TagInput = React.forwardRef((props, ref) => {
  const [tags, setTags] = useState([]); // 待分隔列表
  const [inputVisible, setInputVisible] = useState(false); // 是否显示输入框
  const [inputValue, setInputValue] = useState(''); // 输入框的值
  const {
    title = '新增一个',
    separator = ',',
    maxLength = 40,
    color = 'orange',
    form,
    name,
  } = props;
  const saveInputRef = useRef();

  useEffect(() => {
    if (inputVisible) saveInputRef.current.focus();
  }, [inputVisible]);

  useEffect(() => {
    if (form.getFieldValue(name)) {
      setTags(form.getFieldValue(name).split(separator));
    }
  }, [form.getFieldValue(name)]);

  /*
   * 删除某个tag
   * */
  const handleClose = (removedTag) => {
    const updatedTags = tags.filter((tag) => tag !== removedTag);
    setTags(updatedTags);
    form.setFieldsValue({ [name]: updatedTags?.join(separator) });
  };

  /*
   * 新增一个tag
   * */
  const handleInputConfirm = () => {
    if (inputValue && tags.indexOf(inputValue) === -1) {
      const newTags = [...tags, inputValue];
      setTags(newTags);
      form.setFieldsValue({ [name]: newTags?.join(separator) });
    } else {
      message.error('请正确输入');
    }
    setInputVisible(false);
    setInputValue('');
  };

  return (
    <>
      {tags.map((tag) => {
        const isLongTag = tag.length > maxLength;
        const tagElem = (
          <Tag
            key={tag}
            closable
            onClose={() => handleClose(tag)}
            color={color}
          >
            {isLongTag ? `${tag.slice(0, 20)}...` : tag}
          </Tag>
        );
        return isLongTag ? (
          <Tooltip title={tag} key={tag}>
            {tagElem}
          </Tooltip>
        ) : (
          tagElem
        );
      })}
      {inputVisible && (
        <Input
          ref={saveInputRef}
          type="text"
          size="small"
          style={{ width: 78 }}
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onBlur={handleInputConfirm}
          onPressEnter={handleInputConfirm}
        />
      )}
      {!inputVisible && (
        <Tag
          onClick={() => setInputVisible(true)}
          style={{ background: '#fff', borderStyle: 'dashed' }}
        >
          <Icon type="plus-circle" /> {title}
        </Tag>
      )}
    </>
  );
});

TagInput.propTypes = {
  title: PropTypes.string, // 新增一个tag的标题
  separator: PropTypes.string, // 分隔符
  maxLength: PropTypes.number, // tag最大长度
  color: PropTypes.string, // tag颜色
  form: PropTypes.object, // form
  key: PropTypes.string, // form的key
};

export default TagInput;

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

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

相关文章

基于OpenCV+Keras+tensorflow 实现的变电站作业管控平台源代码。含人脸识别考勤,移动目标跟踪,越线检测,安全措施检测,姿态识别等功能

#综述 使用该作业现场安全生产智能管控平台来实现变电站的安全生产的智能化管理&#xff0c;通过人脸识别功能进行人员的考勤&#xff1b; 通过人员、车辆的检测和识别来实现变电站的智能化管理&#xff1b;通过安全行为识别和安全区域报警功能来实现对变电站内人员和设备安全的…

线程同步与互斥

目录 前言&#xff1a;基于多线程不安全并行抢票 一、线程互斥锁 mutex 1.1 加锁解锁处理多线程并发 1.2 如何看待锁 1.3 如何理解加锁解锁的本质 1.4 CRAII方格设计封装锁 前言&#xff1a;基于线程安全的不合理竞争资源 二、线程同步 1.1 线程同步处理抢票 1.2 如何…

分库分表相关知识

文章目录 一、为什么要分库分表1.1 什么是分库1.2 什么是分表1.3 为什么要分库1.3.1 磁盘存储1.3.2 并发连接支撑 1.4 为什么要分表 二、分库分表解决方案2.1 垂直&#xff08;纵向&#xff09;切分2.1.1 垂直切分优点2.1.2 垂直切分缺点 2.2 水平&#xff08;横向&#xff09;…

video 视频编解码一些debug方法

文章目录 一、通过命令去获取一些数据1.2 确定我们xml配置文件: 二、查看我们芯片支持的编码能力三、通过log去获取信息 这个文章的主要内容是为了后期性能方面的debug, 设计到前期的bringup则没有 一、通过命令去获取一些数据 获取媒体相关的参数&#xff1a; # getprop |…

失效的访问控制

文章目录 渗透测试漏洞原理失效的访问控制1. 失效的访问控制1.1 OWASP TOP 101.1.1 A5:2017-Broken Access Control1.1.2 A01:2021-Broken Access Control 1.2 失效的访问控制类别1.2.1 水平越权1.2.2 垂直越权 1.3 攻防案例1.3.1 DVWA越权 1.4 相关漏洞1.4.1 目录遍历1.4.2 未…

泥石流山体滑坡监控视觉识别检测算法

泥石流山体滑坡监控视觉识别检测算法通过yolov8python深度学习框架模型&#xff0c;泥石流山体滑坡监控视觉识别检测算法识别到泥石流及山体滑坡灾害事件的发生&#xff0c;算法会立即进行图像抓拍&#xff0c;并及时进行预警。Yolo的源码是用C实现的&#xff0c;但是好在Githu…

解决Echarts中双坐标轴分割错位问题

1、处理函数 /*** Description 刻度最大值* date 2023-08-30* param {any} isNaN(maxValue/1* returns {any}*/ export const getYAxisMax (maxValue): number > {if (isNaN(maxValue / 1) || maxValue / 1 < 10) {return 10;}const max: any Math.ceil(maxValue) ;c…

Vue框架--Vue中el和data的两种写法

data与el的2种写法 1.el有2种写法 (1).new Vue时候配置el属性。 (2).先创建Vue实例&#xff0c;随后再通过vm.$mount(#root)指定el的值。 2.data有2种写法 (1).对象式 (2).函数式 如何选择&#xff1a;目前哪种写法都可以&#xff0c;以后学习到组件时&#xff…

Redis-Cluster集群操作--添加节点

一、环境部署 部署好Redis-Cluster集群&#xff0c;参考上个本人的博客&#xff1a;Redis-Cluster集群的部署&#xff08;详细步骤&#xff09;_是胡也是福的博客-CSDN博客 新准备一台机器&#xff0c;修改主机名&#xff0c;关闭防火墙和selinux&#xff0c;参考&#xff1a…

stm32 iap sd卡升级

参考&#xff1a;STM32F4 IAP 跳转 APP问题_stm32程序跳转_古城码农的博客-CSDN博客 app程序改两个位置 1.程序首地址&#xff1a; 2.改中断向量表位移&#xff0c;偏移量和上面一样就可以 然后编译成bin文件就可以了

云原生Kubernetes:二进制部署K8S单Master架构(二)

目录 一、理论 1.K8S单Master架构 2.部署 master 组件 3.部署 Woker Node 组件 4.在master1节点上操作 5.在 node01 节点上操作 6.在 master01 节点上操作 7.在 node01 节点上操作 8.node02 节点部署&#xff08;方法一&#xff09; 二、实验 1.环境 2.部署 master …

使用Python对数据的操作转换

1、列表加值转字典 在Python中&#xff0c;将列表的值转换为字典的键可以使用以下代码&#xff1a; myList ["name", "age", "location"] myDict {k: None for k in myList} print(myDict) 输出&#xff1a; {name: None, age: None, loca…

10. 微积分 - 微分链式法则

文章目录 微分链式法则Hi, 大家好。我是茶桁。 我们上节课讲了导数,并且在最后预告了今天的内容。今天将会是两部分,一部分是「微分」,一部分是「链式法则」。 微分 微分,我们在导论里面提过。它和导数比较像,但是还是有差别的。实际的定义和内容都比较简单,我们先来看…

【数据结构——树】二叉树的遍历(前序、中序、后序、层序)迭代+递归

文章目录 二叉树的定义二叉树的遍历方式前序遍历递归DFS迭代&#xff08;栈&#xff09; 中序遍历递归DFS迭代&#xff08;栈&#xff09; 后序遍历递归DFS迭代&#xff08;栈&#xff09; 层序遍历迭代&#xff08;队列&#xff09; 二叉树的定义 二叉树是一种常见的树状数据…

MySQL表的增删查改以及基本查询样例

文章目录 表的增删查改创建表插入单行全列数据插入多行指定列数据插入失败则更新替换 select全列查询指定列查询查询字段为表达式为查询结果指定别名查询结果去重 where数学小于60的英语在70到100之间的名字为王开头的总分在 200 分以下的语文成绩 > 80 并且不姓王的 结果排…

IDEA设置文件编码

IDEA设置文件编码 File->Settings->Editor->File Encodings 均设置为utf-8 新项目 设置 文件编码 点击New Projects Setup 再点击Settings for New Projects File->Settings->Editor->File Encodings 均设置为utf-8

Spring-TX 事务

目录 一、事务的种类 二、Spring事务管理器 三、事务注解使用 四、事务注解属性 一、事务的种类 1.编程式事务 所谓编程式事务就是用代码手写事务&#xff0c;包含了事务的开始&#xff0c;具体事务&#xff0c;事务的提交和事务的回滚。在这期间就会产生一些冗余问题&am…

Redis 7 第六讲 主从模式(replica)

🌹🌹🌹 此篇开始进入高级篇范围(❤艸`❤) 理论 即主从复制,master以写为主,Slave以读为主。当master数据变化的时候,自动将新的数据异步同步到其它slave数据库。 使用场景 读写分离 容灾备份数据备份水平扩容主从架构 演示案例 注:masterauth、replicaof主…

pytorch如何使用Focal Loss

Focal loss 是 文章 Focal Loss for Dense Object Detection 中提出对简单样本的进行decay的一种损失函数。是对标准的Cross Entropy Loss 的一种改进。 FL对于简单样本&#xff08;p比较大&#xff09;回应较小的loss。 如论文中的图1&#xff0c; 在p0.6时&#xff0c; 标准的…

openGauss学习笔记-57 openGauss 高级特性-并行查询

文章目录 openGauss学习笔记-57 openGauss 高级特性-并行查询57.1 适用场景与限制57.2 资源对SMP性能的影响57.3 其他因素对SMP性能的影响57.4 配置步骤 openGauss学习笔记-57 openGauss 高级特性-并行查询 openGauss的SMP并行技术是一种利用计算机多核CPU架构来实现多线程并行…