当前位置:   article > 正文

react+antd+CheckableTag实现Tag标签单选或多选功能

react+antd+CheckableTag实现Tag标签单选或多选功能

1、效果如下图

实现tag标签单选或多选功能

2、环境准备

1、react18

2、antd 4+

3、功能实现

原理: 封装一个受控组件,接受父组件的参数,数据发现变化后,回传给父组件

1、首先,引入CheckableTag组件和useEffect, useMemo, useState钩子:

  1. import { Tag } from 'antd';
  2. import { useEffect, useMemo, useState } from 'react';
  3. const { CheckableTag } = Tag;

2、然后,定义一个状态变量来存储选中的tag:

const [tagsData, setTagsData] = useState<enumItem[]>();

3、组件可接收的props子属性 如下:

  •  selectedTagsValues: 父组件传入的标签配置枚举列表
  •  value: 已选中的值
  •  startIndex:距离左右节点位置,用于是否将左右节点滑动到可视局域
  •  onChange: 选中的值发生变化时回调

4、创建一个函数来处理tag的选中和取消选中事件:

  1. // handelChange,找到所点击项的索引,并把那一项的checked设置为true
  2. const handleChange = (tag: any, checked: boolean, mode?: string) => {
  3. if (mode !== 'multiple') {
  4. onChange?.(checked ? tag : null);
  5. return;
  6. }
  7. const changeData = (value || []).filter(
  8. (item: any) => item.value !== tag.value,
  9. );
  10. if (checked) {
  11. changeData.push(tag);
  12. }
  13. onChange?.(changeData);
  14. };

 5、最后,使用CheckableTag组件以及tagsData, value值的变化动态来渲染tag列表,并将选中状态和change事件绑定到对应的属性上:

  1. //遍历
  2. const dom = useMemo(() => {
  3. return (
  4. <div id={uniqueKey} className={clsx(['flex'])}>
  5. {(tagsData || []).map((tag: any, index: number) => {
  6. const isHasSelectedTag = isSelectedTag(tag?.value, value);
  7. return (
  8. // eslint-disable-next-line react/jsx-key
  9. <div className={clsx(['self-check-tag'])} key={index}>
  10. <CheckableTag
  11. key={tag.value}
  12. checked={tag.checked || isHasSelectedTag}
  13. onClick={(e: any) => {
  14. scrollIntoViewHandle(
  15. e.target?.parentElement?.parentElement?.parentElement
  16. ?.childNodes,
  17. index,
  18. tagsData?.length || 0,
  19. );
  20. }}
  21. onChange={(checked) => {
  22. handleChange(tag, checked, mode);
  23. }}
  24. >
  25. <div className={clsx([mode === 'multiple' ? 'cur' : ''])}>
  26. {tag.label}
  27. {tag.checked || isHasSelectedTag ? <i></i> : ''}
  28. </div>
  29. </CheckableTag>
  30. </div>
  31. );
  32. })}
  33. </div>
  34. );
  35. }, [tagsData, value]);

6、完整代码如下:

  1. /**
  2. * 公共组件:标签组件
  3. */
  4. import { Tag } from 'antd';
  5. import clsx from 'clsx';
  6. import { useEffect, useMemo, useState } from 'react';
  7. import './index.less';
  8. const { CheckableTag } = Tag;
  9. type enumItem = {
  10. label: string;
  11. value: string;
  12. };
  13. // 距离左右节点的位置的默认值,决定开始、结束节点是否滚到可视区域
  14. const START_INDEX = 3;
  15. /**
  16. * 标签属性配置
  17. * selectedTagsValues: 可选中的标签配置选项
  18. * uniqueKey:组件唯一标识key
  19. * value: 已选中的值
  20. * startIndex:距离左右节点位置,用于是否将左右节点滑动到可视局域
  21. * onChange: 选中的值发生变化时回调
  22. */
  23. interface SelectTagProps {
  24. selectedTagsValues: enumItem[];
  25. uniqueKey?: string;
  26. value?: any;
  27. startIndex?: number;
  28. mode?: string;
  29. onChange?: (values: any) => void;
  30. }
  31. const SelectTag = (props: SelectTagProps) => {
  32. const { selectedTagsValues, uniqueKey, value, startIndex, mode, onChange } =
  33. props;
  34. const [tagsData, setTagsData] = useState<enumItem[]>();
  35. // 点击tag 跳到对应的可视区域
  36. const scrollIntoViewHandle = (node: any, index: number, tagLen: number) => {
  37. const scrollIndex =
  38. startIndex || Math.min(START_INDEX, Math.floor(tagLen / 2));
  39. if (tagLen > 0 && index < scrollIndex) {
  40. node?.[0]?.scrollIntoView({
  41. behavior: 'smooth',
  42. block: 'start',
  43. inline: 'start',
  44. });
  45. return;
  46. }
  47. if (tagLen > 0 && tagLen - index <= scrollIndex) {
  48. node?.[tagLen - 1]?.scrollIntoView({
  49. behavior: 'smooth',
  50. block: 'start',
  51. inline: 'start',
  52. });
  53. return;
  54. }
  55. node?.[index]?.scrollIntoView({
  56. behavior: 'smooth',
  57. block: 'center',
  58. inline: 'center',
  59. });
  60. };
  61. useEffect(() => {
  62. setTagsData(selectedTagsValues);
  63. }, [selectedTagsValues]);
  64. useEffect(() => {
  65. // 若枚举过长,可考虑传入一个uniqueKey,自动调到指定位置
  66. if (uniqueKey && value) {
  67. const index: number = (tagsData || []).findIndex(
  68. (item: any) => item.value && item?.value === value?.value,
  69. );
  70. if (index > -1 && document.getElementById(uniqueKey)) {
  71. setTimeout(() => {
  72. scrollIntoViewHandle(
  73. document.getElementById(uniqueKey)?.childNodes,
  74. index,
  75. tagsData?.length || 0,
  76. );
  77. }, 100);
  78. }
  79. }
  80. }, [value]);
  81. // handelChange,找到所点击项的索引,并把那一项的checked设置为true
  82. const handleChange = (tag: any, checked: boolean, mode?: string) => {
  83. if (mode !== 'multiple') {
  84. onChange?.(checked ? tag : null);
  85. return;
  86. }
  87. const changeData = (value || []).filter(
  88. (item: any) => item.value !== tag.value,
  89. );
  90. if (checked) {
  91. changeData.push(tag);
  92. }
  93. onChange?.(changeData);
  94. };
  95. // tag是否选中
  96. const isSelectedTag = (tagValue: string, value: any) => {
  97. if (mode === 'multiple') {
  98. if (Array.isArray(value) && value.length) {
  99. const findIndex = value.findIndex((item) => item?.value === tagValue);
  100. return findIndex > -1;
  101. }
  102. return false;
  103. }
  104. return tagValue === value?.value;
  105. };
  106. //遍历
  107. const dom = useMemo(() => {
  108. return (
  109. <div id={uniqueKey} className={clsx(['flex'])}>
  110. {(tagsData || []).map((tag: any, index: number) => {
  111. const isHasSelectedTag = isSelectedTag(tag?.value, value);
  112. return (
  113. // eslint-disable-next-line react/jsx-key
  114. <div className={clsx(['self-check-tag'])} key={index}>
  115. <CheckableTag
  116. key={tag.value}
  117. checked={tag.checked || isHasSelectedTag}
  118. onClick={(e: any) => {
  119. scrollIntoViewHandle(
  120. e.target?.parentElement?.parentElement?.parentElement
  121. ?.childNodes,
  122. index,
  123. tagsData?.length || 0,
  124. );
  125. }}
  126. onChange={(checked) => {
  127. handleChange(tag, checked, mode);
  128. }}
  129. >
  130. <div className={clsx([mode === 'multiple' ? 'cur' : ''])}>
  131. {tag.label}
  132. {tag.checked || isHasSelectedTag ? <i></i> : ''}
  133. </div>
  134. </CheckableTag>
  135. </div>
  136. );
  137. })}
  138. </div>
  139. );
  140. }, [tagsData, value]);
  141. return <>{dom}</>;
  142. };
  143. export default SelectTag;

样式文件:

  1. .self-check-tag {
  2. display: flex;
  3. .ant-tag {
  4. display: inline-block;
  5. height: 32px;
  6. font-size: 14px;
  7. font-family: 'Microsoft YaHei';
  8. color: #fff;
  9. line-height: 32px;
  10. border-radius: 3px;
  11. padding-left: 10px;
  12. padding-right: 10px;
  13. }
  14. div.cur {
  15. position: relative;
  16. // padding: 0 12px;
  17. }
  18. div.cur > i {
  19. display: block;
  20. position: absolute;
  21. border-bottom: 16px solid #1890ff;
  22. border-left: 16px solid transparent;
  23. width: 0;
  24. height: 0;
  25. bottom: 1px;
  26. right: -8px;
  27. content: '';
  28. }
  29. div.cur > i::before {
  30. content: '';
  31. position: absolute;
  32. top: -1px;
  33. right: -2px;
  34. border: 16px solid #1890ff;
  35. border-top-color: transparent;
  36. border-left-color: transparent;
  37. }
  38. div.cur > i::after {
  39. content: '';
  40. width: 6px;
  41. height: 10px;
  42. position: absolute;
  43. right: 0;
  44. top: 3px;
  45. border: 1px solid #fff;
  46. border-top-color: transparent;
  47. border-left-color: transparent;
  48. transform: rotate(40deg);
  49. }
  50. .ant-tag-checkable-checked {
  51. color: #2eb3ff;
  52. background-color: rgba(46, 179, 255, 10%);
  53. }
  54. .ant-tag-checkable:hover {
  55. color: #2eb3ff;
  56. background-color: rgba(46, 179, 255, 10%);
  57. }
  58. }

组件调用:

  1. const selectedTagsValues: any[] = [
  2. { label: '特大型', value: '1' },
  3. { label: '大型2级', value: '2' },
  4. { label: '大型3级', value: '3' },
  5. { label: '中型4级', value: '4' },
  6. { label: '中型5级', value: '5' },
  7. { label: '小型', value: '6' },
  8. ]
  9. <SelectTag selectedTagsValues={selectedTagsValues} />

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/79691?site
推荐阅读
相关标签
  

闽ICP备14008679号