当前位置: 首页 > news >正文

React动态添加标签组件

背景

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

需求

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, // formkey: 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方法

<Inputref={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 && (<Inputref={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, // formkey: 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 = (<Tagkey={tag}closableonClose={() => handleClose(tag)}color={color}>{isLongTag ? `${tag.slice(0, 20)}...` : tag}</Tag>);return isLongTag ? (<Tooltip title={tag} key={tag}>{tagElem}</Tooltip>) : (tagElem);})}{inputVisible && (<Inputref={saveInputRef}type="text"size="small"style={{ width: 78 }}value={inputValue}onChange={(e) => setInputValue(e.target.value)}onBlur={handleInputConfirm}onPressEnter={handleInputConfirm}/>)}{!inputVisible && (<TagonClick={() => 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, // formkey: PropTypes.string, // form的key
};export default TagInput;

相关文章:

React动态添加标签组件

背景 在前端开发的过程中,一些表单的输入经常需要输入多个内容,如果采用一个输入框逗号分隔的方式,展示起来不是很清晰,一般需要采用标签的方式 需求 可以指定空状态时的标题设置标签颜色每个标签的最大长度(字符数)接口传递的时候的分隔标记(是用逗号,还是其他)直接处理表单,不…...

[Linux]套接字通信

摘于https://subingwen.cn,作者:苏丙榅 侵删 文章目录 1. 套接字-socket1.1 概念1.2 网络协议1.3 socket编程1.3.1 字节序1.3.2 IP地址转换1.3.3 sockaddr 数据结构1.3.4 套接字函数 1.4 TCP通信流程1.4.1 服务器端通信流程1.4.2 客户端的通信流程 1.5 扩展阅读1.5.1 初始化套…...

MySQL的故事——MySQL架构与历史

MySQL架构与历史 文章目录 MySQL架构与历史一、MySQL逻辑架构二、并发控制三、事务四、多版本并发控制(MVCC) 一、MySQL逻辑架构 第一层&#xff1a;连接处理、授权认证、安全等等 第二层&#xff1a;查询解析、分析、优化、缓存以及所有的内置函数。包含跨存储引擎的功能&…...

手写Mybatis:第12章-完善ORM框架,增删改查操作

文章目录 一、目标&#xff1a;完善增删改查二、设计&#xff1a;完善增删改查三、实现&#xff1a;完善增删改查3.1 工程结构3.2 完善增删改查类图3.3 扩展解析元素3.4 新增执行方法3.4.1 执行器接口添加update3.4.2 执行器抽象基类3.4.3 简单执行器 3.5 语句处理器实现3.5.1 …...

【1】DDR---容量计算

1、容量计算 density&#xff1a;芯片容量&#xff0c;bit为单位 depth&#xff1a;地址空间&#xff0c; width&#xff1a;数据位宽 densitydepth*width 2、三星DDR 4Gbit&#xff08;总容量&#xff09;256M&#xff08;地址空间&#xff09;*16&#xff08;位宽&#xff…...

YashanDB:潜心实干,数据库核心技术突破没有捷径可走

都说数据库是三大基础软件中的一块硬骨头&#xff0c;技术门槛高、研发周期长、工程要求高&#xff0c;市场长期被几大巨头所把持。 因此&#xff0c;实现突破一直是中国数据库产业的夙愿。自上个世纪80年代起&#xff0c;中国数据库产业走过艰辛坎坷的四十余载&#xff0c;终…...

Talk | ICCV‘23南洋理工大学博士后李祥泰:面向统一高效的视频分割方法设计

本期为TechBeat人工智能社区第528期线上Talk&#xff01; 北京时间9月6日(周三)20:00&#xff0c;南洋理工大学博士后研究员—李祥泰的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “面向统一高效的视频分割方法设计”&#xff0c;他分享了其在视…...

怎样把英语视频字幕翻译成中文

我们知道&#xff0c;随着中外文化交流日益频繁&#xff0c;视频翻译作为一种重要的跨文化交流方式&#xff0c;也越来越受到重视。那么&#xff0c;怎样把英语视频翻译成中文&#xff0c;北京视频翻译哪里比较专业&#xff1f; 据了解&#xff0c;视频翻译是直接将一种语言的音…...

智慧铁路:机车整备场数字孪生

机车整备场是铁路运输系统中的重要组成部分&#xff0c;它承担着机车的维修、保养和整备工作&#xff0c;对保障铁路运输的运维和安全起着至关重要的作用。 随着铁路运输的发展、机车技术的不断进步&#xff0c;以及数字化转型的不断推进&#xff0c;数字孪生技术在机车整备场…...

ImageSharp.Web实战:轻松搭建高效图片服务

很多情况下&#xff0c;在开发如PC、H5、小程序等综合平台的时候&#xff0c;图片的展示是个比较头疼的问题。尤其是有会员功能&#xff0c;会员可以上传图片的平台&#xff0c;更是一件麻烦事。平台展示图片的地方&#xff0c;尺寸是定义好的。但用户不配合&#xff0c;上传的…...

端口扫描-安全体系-网络安全技术和协议

端口扫描-安全体系-网络安全技术和协议 端口扫描信息安全的保证体系和评估方法网络安全技术网络攻击和威胁(重要)网络安全协议 端口扫描 全TCP连接:三次握手 半打开式扫描:前两次握手 FIN扫描:不用建立TCP连接 第三方扫描: 拒绝服务攻击有: 同步包风暴ICMP攻击SNMP攻击 都是修改…...

C# wpf 实现截屏框热键截屏功能

wpf截屏系列 第一章 使用GDI实现截屏 第二章 使用DockPanel制作截屏框 第三章 实现截屏框热键截屏&#xff08;本章&#xff09; 第四章 实现截屏框实时截屏 第五章 使用ffmpeg命令行实现录屏 文章目录 wpf截屏系列前言一、实现步骤1、响应热键2、截屏显示&#xff08;1&#…...

springboot + activiti实现activiti微服务化

概述 本文介绍如何将springbootactiviti进行整合,并配合eureka,zuul和feign实现activiti的微服务化,将流程控制和业务逻辑分离. 并实现了几个比较特殊的功能,比如时间段委托(某人请假或出差,出差时间内,所有待办交给被委托人处理),比如节点的无限级加签功能(流程本身有不确定性…...

c语言练习41:深入理解字符串函数strlen strcpy strcat

深入理解字符串函数strlen strcpy strcat 模拟实现&#xff1a;”strlen strcpy strcat strlen strcat: #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<assert.h> strlen 1.通过指针移动模拟 //int my_strlen(char* str) { // size_t c…...

Vue3+Vue-i18n+I18N ALLY+VSCODE 自动翻译多国语言

ps: 效果图放前面,符合的往下看&#xff0c;不符合的出门右转&#xff0c;希望多多点赞评论支持。 三种语言模式&#xff0c;分别是中文、英文、日文 批量翻译 最后的结果 配置vue-i18n 1、下载安装vue-i18n&#xff0c;9以上的版本。 2、创建对应文件夹 3、对应文件夹中代…...

idea意外退出mac

目录 问题描述 解决过程 问题描述 mac上的idea我很久没用了&#xff0c;之前用的时候还是发布新版的开源项目&#xff0c;这几天再用的时候&#xff0c;就出现了idea意外退出的问题&#xff0c;我上网查找了很久&#xff0c;对于我的问题都没有很好的解决。 解决过程 在寻求…...

百度智能云千帆大模型平台2.0来了!从大模型到生产力落地的怪兽级平台!!

目录 前言 最佳算力效能为企业降低门槛 最多大模型&#xff0c;最多数据集为企业保驾护航 企业级安全对于企业来说是硬性要求 前言 普通人或许感知不明显&#xff0c;但是对于企业而言&#xff0c;身处AI时代&#xff0c;是否选择投资大模型&#xff0c;是否拥抱人工智能…...

k8s nfs-client 添加挂载参数 —— 筑梦之路

背景介绍 为什么要使用noresvport参数挂载NAS&#xff1f;不重新挂载会有什么后果&#xff1f; 如果发生网络切换或者后端服务的HA倒换&#xff0c;小概率会造成NFS文件系统阻塞&#xff0c;那就可能需要几分钟时间连接才会自动恢复&#xff0c;极端情况下甚至需要重启ECS才能恢…...

【算法】堆排序 详解

堆排序 详解 堆排序代码实现 排序&#xff1a; 排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a; 假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c…...

解决Maven依赖下载问题:从阿里云公共仓库入手

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

迁移科技3D视觉系统:重塑纸箱拆垛场景的智能革命

一、传统拆垛场景的困局与破局之道 在汽车零部件仓库中&#xff0c;每天有超过2万只异形纸箱需要拆垛分拣。传统人工拆垛面临三大挑战&#xff1a; 效率瓶颈&#xff1a;工人每小时仅能处理200-300件&#xff0c;且存在间歇性疲劳安全隐患&#xff1a;20kg以上重箱搬运导致年…...

【靶场】XXE-Lab xxe漏洞

前言 学习xxe漏洞,搭了个XXE-Lab的靶场 一、搭建靶场 现在需要登录,不知道用户名密码,先随便试试抓包 二、判断是否存在xxe漏洞 1.首先登录抓包 看到xml数据解析,由此判断和xxe漏洞有关,但还不确定xxe漏洞是否存在。 2.尝试xxe 漏洞 判断是否存在xxe漏洞 A.send to …...

ubuntu系统 | docker+dify+ollama+deepseek搭建本地应用

1、docker 介绍与安装 docker安装:1、Ubuntu系统安装docker_ubuntu docker run-CSDN博客 docker介绍及镜像源配置:2、ubuntu系统docker介绍及镜像源和仓库配置-CSDN博客 docker常用命令:3、ubuntu系统docker常用命令-CSDN博客 docker compose安装:4、docker compose-CS…...