index.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import React, { useState, useEffect, useRef, useCallback } from 'react';
  2. import { View, ScrollView } from '@tarojs/components';
  3. import './CategorySidebar.css';
  4. export interface CategorySidebarProps {
  5. /** 当前选中的分类索引 */
  6. activeKey?: number;
  7. /** 自定义类名 */
  8. className?: string;
  9. /** 选中项改变时的回调 */
  10. onChange?: (index: number) => void;
  11. /** 子组件 */
  12. children?: React.ReactNode;
  13. }
  14. interface CategorySidebarContextType {
  15. registerItem: (item: any) => number;
  16. unregisterItem: (item: any) => void;
  17. setActive: (index: number) => Promise<void>;
  18. activeKey: number;
  19. }
  20. export const CategorySidebarContext = React.createContext<CategorySidebarContextType | null>(null);
  21. const CategorySidebar: React.FC<CategorySidebarProps> = (props) => {
  22. const { activeKey = 0, className = '', onChange, children } = props;
  23. const [currentActive, setCurrentActive] = useState<number>(activeKey);
  24. const [childrenList, setChildrenList] = useState<any[]>([]);
  25. const [topRightRadiusItemIndexs, setTopRightRadiusItemIndexs] = useState<number[]>([]);
  26. const [bottomRightRadiusItemIndexs, setBottomRightRadiusItemIndexs] = useState<number[]>([]);
  27. const childrenRef = useRef<any[]>([]);
  28. const currentActiveRef = useRef<number>(currentActive);
  29. const topRightRadiusItemIndexsRef = useRef<number[]>([]);
  30. const bottomRightRadiusItemIndexsRef = useRef<number[]>([]);
  31. // 更新 ref 值
  32. useEffect(() => {
  33. childrenRef.current = childrenList;
  34. currentActiveRef.current = currentActive;
  35. topRightRadiusItemIndexsRef.current = topRightRadiusItemIndexs;
  36. bottomRightRadiusItemIndexsRef.current = bottomRightRadiusItemIndexs;
  37. }, [childrenList, currentActive, topRightRadiusItemIndexs, bottomRightRadiusItemIndexs]);
  38. // 注册子组件
  39. const registerItem = useCallback((item: any) => {
  40. const currentLength = childrenRef.current.length;
  41. const itemIndex = currentLength;
  42. setChildrenList(prev => {
  43. const newList = [...prev, item];
  44. childrenRef.current = newList;
  45. //console.debug('CategorySidebar registerItem:', { itemIndex, totalItems: newList.length });
  46. return newList;
  47. });
  48. return itemIndex;
  49. }, []);
  50. // 注销子组件
  51. const unregisterItem = useCallback((item: any) => {
  52. setChildrenList(prev => {
  53. const newList = prev.filter(i => i !== item);
  54. childrenRef.current = newList;
  55. return newList;
  56. });
  57. }, []);
  58. // 计算顶部圆角项索引
  59. const getTopRightRadiusItemIndexs = useCallback((activeKey: number, children: any[]) => {
  60. const { length } = children;
  61. if (activeKey !== 0 && activeKey < length - 1) return [0, activeKey + 1];
  62. if (activeKey !== 0) return [0];
  63. if (activeKey < length - 1) return [activeKey + 1];
  64. return [];
  65. }, []);
  66. // 计算底部圆角项索引
  67. const getBottomRightRadiusItemIndexs = useCallback((activeKey: number) => {
  68. if (activeKey !== 0) return [activeKey - 1];
  69. return [];
  70. }, []);
  71. // 设置选中状态
  72. const setActive = useCallback(async (activeKey: number, isChildrenChange = false) => {
  73. const children = childrenRef.current;
  74. const currentActive = currentActiveRef.current;
  75. const preTopRightRadiusItemIndexs = topRightRadiusItemIndexsRef.current;
  76. const preBottomRightRadiusItemIndexs = bottomRightRadiusItemIndexsRef.current;
  77. if (!children.length) {
  78. // console.debug('CategorySidebar setActive: no children available');
  79. return;
  80. }
  81. // 如果是子组件变化触发的,即使 activeKey 相同也要重新设置样式
  82. // 或者如果是用户点击同一个item,也需要重新设置样式
  83. if (activeKey === currentActive && !isChildrenChange) {
  84. // 即使点击同一个item,也要确保样式正确应用
  85. //console.debug('CategorySidebar setActive: same item clicked, ensuring styles');
  86. }
  87. // console.debug('CategorySidebar setActive:', { activeKey, currentActive, isChildrenChange, childrenCount: children.length });
  88. // 确保 activeKey 在有效范围内
  89. if (activeKey < 0 || activeKey >= children.length) {
  90. // console.debug('CategorySidebar setActive: invalid activeKey, using 0');
  91. setCurrentActive(0);
  92. return;
  93. }
  94. setCurrentActive(activeKey);
  95. const newTopRightRadiusItemIndexs = getTopRightRadiusItemIndexs(activeKey, children);
  96. const newBottomRightRadiusItemIndexs = getBottomRightRadiusItemIndexs(activeKey);
  97. // console.debug('CategorySidebar setActive - radius indexes:', {
  98. // top: newTopRightRadiusItemIndexs,
  99. // bottom: newBottomRightRadiusItemIndexs
  100. // });
  101. setTopRightRadiusItemIndexs(newTopRightRadiusItemIndexs);
  102. setBottomRightRadiusItemIndexs(newBottomRightRadiusItemIndexs);
  103. const promises: Promise<void>[] = [];
  104. // 移除旧的圆角效果
  105. preTopRightRadiusItemIndexs.forEach((item) => {
  106. if (children[item]) {
  107. promises.push(children[item].setTopRightRadius(false));
  108. }
  109. });
  110. preBottomRightRadiusItemIndexs.forEach((item) => {
  111. if (children[item]) {
  112. promises.push(children[item].setBottomRightRadius(false));
  113. }
  114. });
  115. // 应用新的圆角效果
  116. newTopRightRadiusItemIndexs.forEach((item) => {
  117. if (children[item]) {
  118. promises.push(children[item].setTopRightRadius(true));
  119. }
  120. });
  121. newBottomRightRadiusItemIndexs.forEach((item) => {
  122. if (children[item]) {
  123. promises.push(children[item].setBottomRightRadius(true));
  124. }
  125. });
  126. await Promise.all(promises);
  127. // 触发 onChange 回调
  128. if (onChange && !isChildrenChange) {
  129. onChange(activeKey);
  130. }
  131. }, [getTopRightRadiusItemIndexs, getBottomRightRadiusItemIndexs, onChange]);
  132. // 监听 activeKey 变化
  133. useEffect(() => {
  134. if (activeKey !== currentActive) {
  135. setActive(activeKey);
  136. }
  137. }, [activeKey, setActive]);
  138. // 组件挂载时设置初始选中状态
  139. useEffect(() => {
  140. if (childrenList.length > 0 && currentActive === activeKey) {
  141. setActive(activeKey, true);
  142. }
  143. }, [childrenList, setActive, currentActive, activeKey]);
  144. // 子组件变化时重新设置选中状态
  145. useEffect(() => {
  146. if (childrenList.length > 0) {
  147. setActive(currentActive, true);
  148. }
  149. }, [childrenList, setActive]);
  150. const contextValue: CategorySidebarContextType = {
  151. registerItem,
  152. unregisterItem,
  153. setActive,
  154. activeKey: currentActive,
  155. };
  156. return (
  157. <CategorySidebarContext.Provider value={contextValue}>
  158. <ScrollView
  159. className={`category-sidebar ${className}`}
  160. scrollY
  161. scrollWithAnimation
  162. >
  163. <View className="category-sidebar__content">
  164. {children}
  165. </View>
  166. </ScrollView>
  167. </CategorySidebarContext.Provider>
  168. );
  169. };
  170. export default CategorySidebar;