当前位置: 首页 > 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; 文章图文…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...