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} />

下一节将分享多层级的标签选中功能,同时支持多选和单选功能

下拉多选可以使用antd-mobile的CheckboxItem组件来实现。具体代码如下: ```jsx import React, { useState } from 'react'; import { List, Checkbox } from 'antd-mobile'; const CheckboxItem = Checkbox.CheckboxItem; const options = [ { label: '选项1', value: '1' }, { label: '选项2', value: '2' }, { label: '选项3', value: '3' }, { label: '选项4', value: '4' }, ]; const MultipleSelect = () => { const [selectedValues, setSelectedValues] = useState([]); const handleCheckboxChange = (value) => { const currentIndex = selectedValues.indexOf(value); const newValues = [...selectedValues]; if (currentIndex === -1) { newValues.push(value); } else { newValues.splice(currentIndex, 1); } setSelectedValues(newValues); }; return ( <List> {options.map((option) => ( <CheckboxItem key={option.value} checked={selectedValues.indexOf(option.value) !== -1} onChange={() => handleCheckboxChange(option.value)} > {option.label} </CheckboxItem> ))} </List> ); }; export default MultipleSelect; ``` 上面的代码中,我们定义了一个`options`数组来存储选项列表。然后使用`useState`来管理已选中的值,初始值为空数组。在`handleCheckboxChange`方法中,我们根据选项的值来判断它是否已经被选中,如果已经被选中,则从已选中的值数组中删除该值,否则将该值添加到已选中的值数组中。最后通过`setSelectedValues`更新已选中的值数组。 在渲染时,我们遍历选项列表,并为每个选项渲染一个`CheckboxItem`组件。我们使用`checked`属性来判断该选项是否已经被选中,使用`onChange`事件来处理选项的选择和取消选择操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值