赞
踩
实现tag标签单选或多选功能
1、react18
2、antd 4+
原理: 封装一个受控组件,接受父组件的参数,数据发现变化后,回传给父组件
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子属性 如下:
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} />
下一节将分享多层级的标签选中功能,同时支持多选和单选功能
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。