react组件篇之自定义多选下拉列表

为满足特定项目需求,本文介绍如何利用React与Ant Design组件库中的Popover及自定义TagButton组件,实现一个多选下拉列表功能。该组件允许用户通过点击添加标签,并能够动态显示已选择的选项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

近期因公司项目需求,需要做一个多选的下拉列表。找了许多UI框架都没有找到适合的,因此决定自己封装一个并分享出来供有需求的伙伴们使用。项目需求如下图所示:

该需求是当点击添加标签时会弹出一个下拉列表,选中某一项就会在前面添加,再次点击该选中项时则取消选中,添加项消失。

2.实现思路

最初想法是用ant-design Tag和Dropdown实现,由于Dropdown会屏蔽自定义事件而且Tag在close时会因为销毁组件而取不到目标值。因此最后使用Popover以及自己封装的TagButton组件来实现该需求。本篇文件只讲实现过程,具体的依赖安装小伙伴们可以自己百度。

3.实现代码

由于时间原因只是实现了该需求的功能,具体样式在这里就不说了,实现的组件如下图所示:

下面附上实现代码,自取所需:

index.jsx

import React, { Component } from 'react';
import { Menu, Dropdown, Icon, Tag, Popover, Button } from 'antd';
import TagButton from './tag'
require('./index.less')
export default class SourceDemo extends Component {

    /***********************/
    /*父组件传值接口sectList*/
    /**********************/
    state = {
        selectList:[],
        tagList:[],
        selectedKeys: []
    }
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        this.setState({selectList: this.props.sectList});
    }
    handleButtonSelect = (e) => {
        this.setState({selectedKeys: e.selectedKeys})
        this.state.tagList.push(e.item.props.value)
    }
    handleButtonDeSelect = (e) => {
        let delValue =  this.state.tagList
        delValue.splice(delValue.findIndex(item => item.id == e.key), 1)
        this.setState({selectedKeys: e.selectedKeys, tagList: delValue})
    }
    handleShow = (itemId) => {
        let deValue =  this.state.tagList
        let selKeys = this.state.selectedKeys
        deValue.splice(deValue.findIndex(item => item.id == itemId), 1)
        selKeys.splice(selKeys.findIndex(item => item.id == itemId), 1)
        this.setState({tagList: deValue, selectedKeys:selKeys})
    }
    render() {
        const menu = (
            <Menu selectedKeys={this.state.selectedKeys} multiple={true} onSelect={this.handleButtonSelect} onDeselect={this.handleButtonDeSelect}>
              {
                  this.state.selectList.map((item,index) => (
                    <Menu.Item value={item} key={item.id}>
                       <a>{item.title}</a>
                    </Menu.Item>
                   ))
              }
            </Menu>
          );
        return (
            <div className="multi-select-box">
                {
                    this.state.tagList.map((item, index) => (
                        <TagButton key={item.id} itemTitle={item.title} itemId={item.id} handleShow={this.handleShow}></TagButton>
                    ))
                }
                <Popover overlayClassName='multi-select-popover' placement="bottomLeft" content={menu} trigger="click">
                    <Button>BL</Button>
                </Popover>
            </div>
        )
    }
}import React, { Component } from 'react'
require('./tag.less')
export default class CustomRemoteSelect extends Component {
    state = { 
    }
    constructor(props) {
        super(props)
    }
    render() {
            return ( 
                    <div className="tag-wrapper">
                        {this.props.itemTitle}
                        <i className="anticon" onClick={() => this.props.handleShow(this.props.itemId)}></i>
                    </div>
            )
        }

    }
复制代码
tag.jsx

import React, { Component } from 'react'
require('./tag.less')
export default class CustomRemoteSelect extends Component {
    state = { 
    }
    constructor(props) {
        super(props)
    }
    render() {
            return ( 
                    <div className="tag-wrapper">
                        {this.props.itemTitle}
                        <i className="anticon" onClick={() => this.props.handleShow(this.props.itemId)}></i>
                    </div>
            )
        }

    }
复制代码
index.less

.multi-select-popover{
    .ant-popover-inner-content{
        padding:0;
    }
}
复制代码
tag.less

.tag-wrapper{
    color:rgba(0, 0, 0, 0.65);
    font-family: "Monospaced Number", "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
    box-sizing: border-box;
    margin: 0;
    list-style: none;
    display: inline-block;
    line-height: 20px;
    height: 22px;
    padding: 0 7px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    background: #fafafa;
    font-size: 12px;
    transition: all 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
    opacity: 1;
    margin-right: 8px;
    cursor: pointer;
    white-space: nowrap;
}
.tag-wrapper {
    .anticon{
        font-size:12px;
        font-style: normal;
        vertical-align: baseline;
        text-align: center;
        text-transform: none;
        line-height: 1;
        text-rendering: optimizeLegibility;
        -webkit-font-smoothing: antialiased;
    }
    .anticon:before {
        content: "\E633";
        display: block;
        font-family: "anticon" !important;
    }
    .anticon:hover{
        color:rgba(0,0,0,0.95);
    }
}
复制代码

感谢阅读本文的小伙伴们,希望本文对你们有用,有错的地方望大佬们指出,不喜勿喷谢谢!

参考链接

ant-design

转载于:https://juejin.im/post/5acb6cc8f265da2398679230

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值