Parcourir la source

✨ feat(category): 添加分类侧边栏组件

- 创建 CategorySidebar 组件,包含 CSS 样式文件和 TypeScript 实现
- 实现分类项注册/注销机制和选中状态管理
- 添加滚动条隐藏样式,优化视觉效果
- 支持自定义类名和选中项变化回调
- 实现圆角效果动态计算,提升UI体验
yourname il y a 1 mois
Parent
commit
14b7b845e4

+ 23 - 0
mini/src/components/category/CategorySidebar/CategorySidebar.css

@@ -0,0 +1,23 @@
+/* CategorySidebar 组件样式 */
+.category-sidebar {
+  width: 176rpx;
+  height: 100vh;
+}
+
+.category-sidebar__content {
+  display: flex;
+  flex-direction: column;
+}
+
+/* 隐藏滚动条 */
+.category-sidebar::-webkit-scrollbar {
+  width: 0;
+  height: 0;
+  color: transparent;
+}
+
+/* 兼容不同浏览器的滚动条隐藏 */
+.category-sidebar {
+  scrollbar-width: none; /* Firefox */
+  -ms-overflow-style: none; /* IE and Edge */
+}

+ 184 - 0
mini/src/components/category/CategorySidebar/index.tsx

@@ -0,0 +1,184 @@
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import { View, ScrollView } from '@tarojs/components';
+import './CategorySidebar.css';
+
+export interface CategorySidebarProps {
+  /** 当前选中的分类索引 */
+  activeKey?: number;
+  /** 自定义类名 */
+  className?: string;
+  /** 选中项改变时的回调 */
+  onChange?: (index: number) => void;
+  /** 子组件 */
+  children?: React.ReactNode;
+}
+
+interface CategorySidebarContextType {
+  registerItem: (item: any) => void;
+  unregisterItem: (item: any) => void;
+  setActive: (index: number) => Promise<void>;
+  activeKey: number;
+}
+
+export const CategorySidebarContext = React.createContext<CategorySidebarContextType | null>(null);
+
+const CategorySidebar: React.FC<CategorySidebarProps> = (props) => {
+  const { activeKey = 0, className = '', onChange, children } = props;
+
+  const [currentActive, setCurrentActive] = useState<number>(activeKey);
+  const [childrenList, setChildrenList] = useState<any[]>([]);
+  const [topRightRadiusItemIndexs, setTopRightRadiusItemIndexs] = useState<number[]>([]);
+  const [bottomRightRadiusItemIndexs, setBottomRightRadiusItemIndexs] = useState<number[]>([]);
+
+  const childrenRef = useRef<any[]>([]);
+  const currentActiveRef = useRef<number>(currentActive);
+  const topRightRadiusItemIndexsRef = useRef<number[]>([]);
+  const bottomRightRadiusItemIndexsRef = useRef<number[]>([]);
+
+  // 更新 ref 值
+  useEffect(() => {
+    childrenRef.current = childrenList;
+    currentActiveRef.current = currentActive;
+    topRightRadiusItemIndexsRef.current = topRightRadiusItemIndexs;
+    bottomRightRadiusItemIndexsRef.current = bottomRightRadiusItemIndexs;
+  }, [childrenList, currentActive, topRightRadiusItemIndexs, bottomRightRadiusItemIndexs]);
+
+  // 注册子组件
+  const registerItem = useCallback((item: any) => {
+    setChildrenList(prev => {
+      const newList = [...prev, item];
+      childrenRef.current = newList;
+      return newList;
+    });
+  }, []);
+
+  // 注销子组件
+  const unregisterItem = useCallback((item: any) => {
+    setChildrenList(prev => {
+      const newList = prev.filter(i => i !== item);
+      childrenRef.current = newList;
+      return newList;
+    });
+  }, []);
+
+  // 计算顶部圆角项索引
+  const getTopRightRadiusItemIndexs = useCallback((activeKey: number, children: any[]) => {
+    const { length } = children;
+    if (activeKey !== 0 && activeKey < length - 1) return [0, activeKey + 1];
+    if (activeKey !== 0) return [0];
+    if (activeKey < length - 1) return [activeKey + 1];
+    return [];
+  }, []);
+
+  // 计算底部圆角项索引
+  const getBottomRightRadiusItemIndexs = useCallback((activeKey: number) => {
+    if (activeKey !== 0) return [activeKey - 1];
+    return [];
+  }, []);
+
+  // 设置选中状态
+  const setActive = useCallback(async (activeKey: number, isChildrenChange = false) => {
+    const children = childrenRef.current;
+    const currentActive = currentActiveRef.current;
+    const preTopRightRadiusItemIndexs = topRightRadiusItemIndexsRef.current;
+    const preBottomRightRadiusItemIndexs = bottomRightRadiusItemIndexsRef.current;
+
+    if (!children.length) {
+      return;
+    }
+
+    if (activeKey === currentActive && !isChildrenChange) {
+      return;
+    }
+
+    setCurrentActive(activeKey);
+
+    const newTopRightRadiusItemIndexs = getTopRightRadiusItemIndexs(activeKey, children);
+    const newBottomRightRadiusItemIndexs = getBottomRightRadiusItemIndexs(activeKey);
+
+    setTopRightRadiusItemIndexs(newTopRightRadiusItemIndexs);
+    setBottomRightRadiusItemIndexs(newBottomRightRadiusItemIndexs);
+
+    const promises: Promise<void>[] = [];
+
+    // 将旧的选中项改为 false
+    if (currentActive !== activeKey && children[currentActive]) {
+      promises.push(children[currentActive].setActive(false));
+    }
+
+    // 将新的选中项改为 true
+    if (children && children[activeKey]) {
+      promises.push(children[activeKey].setActive(true));
+    }
+
+    // 移除旧的圆角效果
+    preTopRightRadiusItemIndexs.forEach((item) => {
+      if (children[item]) {
+        promises.push(children[item].setTopRightRadius(false));
+      }
+    });
+
+    preBottomRightRadiusItemIndexs.forEach((item) => {
+      if (children[item]) {
+        promises.push(children[item].setBottomRightRadius(false));
+      }
+    });
+
+    // 应用新的圆角效果
+    newTopRightRadiusItemIndexs.forEach((item) => {
+      if (children[item]) {
+        promises.push(children[item].setTopRightRadius(true));
+      }
+    });
+
+    newBottomRightRadiusItemIndexs.forEach((item) => {
+      if (children[item]) {
+        promises.push(children[item].setBottomRightRadius(true));
+      }
+    });
+
+    await Promise.all(promises);
+
+    // 触发 onChange 回调
+    if (onChange && !isChildrenChange) {
+      onChange(activeKey);
+    }
+  }, [getTopRightRadiusItemIndexs, getBottomRightRadiusItemIndexs, onChange]);
+
+  // 监听 activeKey 变化
+  useEffect(() => {
+    if (activeKey !== currentActive) {
+      setActive(activeKey);
+    }
+  }, [activeKey, setActive]);
+
+  // 子组件变化时重新设置选中状态
+  useEffect(() => {
+    if (childrenList.length > 0) {
+      setActive(currentActive, true);
+    }
+  }, [childrenList.length, setActive]);
+
+  const contextValue: CategorySidebarContextType = {
+    registerItem,
+    unregisterItem,
+    setActive,
+    activeKey: currentActive,
+  };
+
+  return (
+    <CategorySidebarContext.Provider value={contextValue}>
+      <ScrollView
+        className={`category-sidebar ${className}`}
+        scrollY
+        scrollWithAnimation
+      >
+        <View className="category-sidebar__content">
+          {children}
+        </View>
+      </ScrollView>
+    </CategorySidebarContext.Provider>
+  );
+};
+
+export default CategorySidebar;