diff --git a/docs/z-index-guide.md b/docs/z-index-guide.md new file mode 100644 index 0000000..414d9ad --- /dev/null +++ b/docs/z-index-guide.md @@ -0,0 +1,105 @@ +# Z-Index 层级管理规范 + +## 层级划分 + +为了避免 z-index 冲突,项目采用以下层级划分: + +``` +0-99: 页面内容层(普通元素、卡片、列表项等) +100-899: 固定定位元素(顶部导航栏、sticky 元素等) +900-999: 底部导航栏和菜单 +1000-1999: 下拉菜单、筛选面板 +2000-8999: 模态框、弹窗 +9000+: 全局提示、Toast、确认框 +``` + +## 具体层级分配 + +### 页面内容层 (0-99) +- **0-9**: 背景层 +- **10-99**: 普通内容元素 + +### 固定定位层 (100-899) +- **100**: 页面头部固定区域(.fixedHeader) +- **100-199**: 顶部导航栏、搜索栏 +- **200-899**: 其他 sticky 元素 + +### 底部导航和菜单层 (900-999) +- **80**: 底部导航栏降低层级(GuideBar - 筛选弹窗显示时) +- **900**: 底部导航栏正常层级(GuideBar - 默认状态) +- **940**: 发布菜单遮罩层(PublishMenu overlay) +- **950**: 发布菜单容器(PublishMenu container) +- **960**: 发布菜单卡片(PublishMenu card) + +### 下拉菜单层 (1000-1999) +- **1100**: 距离筛选下拉菜单(DistanceQuickFilter) +- **1200**: 综合筛选弹窗(FilterPopup) + +### 模态框层 (2000-8999) +- **2000-5000**: 普通弹窗 +- **9999**: CommonPopup(全局通用弹窗) + +## 动态 Z-Index 控制 + +某些组件需要根据交互状态动态调整 z-index: + +### GuideBar(底部导航栏)动态控制 + +**实现位置**: +- `src/game_pages/list/index.tsx`(主逻辑) +- `src/components/DistanceQuickFilter/index.tsx`(筛选菜单回调) +- `src/components/PublishMenu/PublishMenu.tsx`(发布菜单回调) +- `src/components/GuideBar/index.tsx`(接收回调) + +**监听的状态**: +1. **`isPublishMenuVisible`** - 发布菜单展开状态 +2. **`isShowFilterPopup`** - 综合筛选弹窗展开状态 +3. **`isDistanceFilterVisible`** - 距离/排序筛选下拉菜单展开状态 + +**动态逻辑**: +```tsx +if (isPublishMenuVisible) { + // 发布菜单展开 → z-index: 900 (high) + setGuideBarZIndex('high'); +} else if (isShowFilterPopup || isDistanceFilterVisible) { + // 任何筛选组件展开 → z-index: 80 (low) + setGuideBarZIndex('low'); +} else { + // 都关闭 → z-index: 900 (high) + setGuideBarZIndex('high'); +} +``` + +**优势**: +- 自动响应所有相关组件的状态变化 +- 优先级清晰:发布菜单 > 筛选组件 > 默认状态 +- 避免底部导航栏遮挡筛选内容 + +## 使用原则 + +1. **同类组件使用相近的 z-index**:便于管理和维护 +2. **预留足够的间隔**:为未来新增组件预留空间 +3. **避免使用过大的值**:除非是全局级别的组件(如 Toast) +4. **使用 !important 需谨慎**:只在覆盖第三方组件样式时使用 +5. **优先考虑动态控制**:对于有交互冲突的组件,使用动态 z-index 而不是固定值 + +## 常见问题 + +### Q: 筛选下拉菜单被底部导航栏遮挡? +A: 确保下拉菜单 z-index (1100) 大于底部导航栏 (900) + +### Q: 发布菜单弹出时被筛选菜单遮挡? +A: 发布菜单 (950-960) 低于筛选菜单 (1100-1200),这是正常的层级关系 + +### Q: 如何添加新的浮层组件? +A: 根据组件类型,在对应层级范围内选择合适的值,并更新此文档 + +## 修改记录 + +- 2024-xx-xx: 完善 GuideBar 动态 z-index 控制,监听所有筛选组件和发布菜单状态 + - 新增 DistanceQuickFilter 菜单展开/收起回调 + - 新增 PublishMenu 展开/收起回调 + - 使用 useEffect 统一管理 z-index 逻辑 +- 2024-xx-xx: 实现 GuideBar 动态 z-index 控制,根据筛选弹窗状态自动调整层级 +- 2024-xx-xx: 统一调整底部导航栏和筛选菜单的 z-index,解决层级冲突问题 + diff --git a/src/components/DistanceQuickFilter/index.scss b/src/components/DistanceQuickFilter/index.scss index e10c615..1f5b485 100644 --- a/src/components/DistanceQuickFilter/index.scss +++ b/src/components/DistanceQuickFilter/index.scss @@ -41,7 +41,7 @@ border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; background-color: #fafafa !important; - z-index: 1000 !important; + z-index: 1100 !important; box-sizing: border-box !important; } diff --git a/src/components/DistanceQuickFilter/index.tsx b/src/components/DistanceQuickFilter/index.tsx index 00583cf..642b1b3 100644 --- a/src/components/DistanceQuickFilter/index.tsx +++ b/src/components/DistanceQuickFilter/index.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useRef, useState, useEffect } from "react"; import { Menu } from "@nutui/nutui-react-taro"; import { Image, View } from "@tarojs/components"; import img from "@/config/images"; @@ -14,10 +14,12 @@ const DistanceQuickFilter = (props) => { quickName, cityValue, quickValue, + onMenuVisibleChange, // 新增:菜单展开/收起回调 } = props; const cityRef = useRef(null); const quickRef = useRef(null); const [changePosition, setChangePosition] = useState([]); + const [isMenuOpen, setIsMenuOpen] = useState(false); // 全城筛选显示的标题 const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label; @@ -48,10 +50,17 @@ const DistanceQuickFilter = (props) => { index === 1 && (quickRef.current as any)?.toggle(false); }; + // 监听菜单状态变化,通知父组件 + useEffect(() => { + onMenuVisibleChange?.(isMenuOpen); + }, [isMenuOpen, onMenuVisibleChange]); + return ( setIsMenuOpen(true)} + onClose={() => setIsMenuOpen(false)} > { onClose={onClose} style={{ marginTop: statusNavbarHeigh + "px", maxHeight: '70vh' }} overlayStyle={{ marginTop: statusNavbarHeigh + "px" }} - zIndex={1001} + zIndex={1200} > {/* 可滚动的内容区域 */} diff --git a/src/components/GuideBar/index.scss b/src/components/GuideBar/index.scss index 96f4df2..bafea3e 100644 --- a/src/components/GuideBar/index.scss +++ b/src/components/GuideBar/index.scss @@ -14,7 +14,7 @@ justify-content: space-between; align-items: center; padding: 20px 12px 32px; - z-index: 999; + z-index: 900; &-pages { display: flex; diff --git a/src/components/GuideBar/index.tsx b/src/components/GuideBar/index.tsx index 60522bf..4fb476e 100644 --- a/src/components/GuideBar/index.tsx +++ b/src/components/GuideBar/index.tsx @@ -7,7 +7,7 @@ import PublishMenu from "../PublishMenu"; export type currentPageType = "games" | "message" | "personal"; const GuideBar = (props) => { - const { currentPage, guideBarClassName } = props; + const { currentPage, guideBarClassName, onPublishMenuVisibleChange } = props; const guideItems = [ { @@ -73,7 +73,7 @@ const GuideBar = (props) => { {/* */} - + ); diff --git a/src/components/PublishMenu/PublishMenu.tsx b/src/components/PublishMenu/PublishMenu.tsx index 90c7ee2..26b44a0 100644 --- a/src/components/PublishMenu/PublishMenu.tsx +++ b/src/components/PublishMenu/PublishMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useEffect } from "react"; import { View, Text, Image } from "@tarojs/components"; import Taro from "@tarojs/taro"; import { useUserInfo } from "@/store/userStore"; @@ -15,10 +15,17 @@ import NTRPEvaluatePopup from "../NTRPEvaluatePopup"; export interface PublishMenuProps { onPersonalPublish?: () => void; onActivityPublish?: () => void; + onVisibleChange?: (visible: boolean) => void; // 菜单显示/隐藏回调 } -const PublishMenu: React.FC = () => { +const PublishMenu: React.FC = (props) => { + const { onVisibleChange } = props; const [isVisible, setIsVisible] = useState(false); + + // 使用 useEffect 监听 isVisible 变化,确保所有情况都能触发回调 + useEffect(() => { + onVisibleChange?.(isVisible); + }, [isVisible, onVisibleChange]); const [aiImportVisible, setAiImportVisible] = useState(false); const userInfo = useUserInfo(); const ntrpRef = useRef<{ diff --git a/src/components/PublishMenu/index.module.scss b/src/components/PublishMenu/index.module.scss index c234220..a003d6f 100644 --- a/src/components/PublishMenu/index.module.scss +++ b/src/components/PublishMenu/index.module.scss @@ -1,6 +1,6 @@ .publishMenu { position: relative; - z-index: 1000; + z-index: 950; } .overlay { @@ -10,7 +10,7 @@ width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); - z-index: 999; + z-index: 940; } .menuCard { position: absolute; @@ -22,7 +22,7 @@ display: flex; flex-direction: column; gap: 8px; - z-index: 1001; + z-index: 960; // /* 小三角指示器 */ // &::after { // content: ''; diff --git a/src/container/listContainer/index.tsx b/src/container/listContainer/index.tsx index 459acd5..6d1439c 100644 --- a/src/container/listContainer/index.tsx +++ b/src/container/listContainer/index.tsx @@ -63,7 +63,7 @@ const ListContainer = (props) => { // 延迟 300ms 后再显示骨架屏 skeletonTimerRef.current = setTimeout(() => { setShowSkeleton(true); - }, 300); + }, 600); } else { // 加载完成,清除定时器并隐藏骨架屏 if (skeletonTimerRef.current) { diff --git a/src/game_pages/list/index.module.scss b/src/game_pages/list/index.module.scss index c8b3cf2..3c9f053 100644 --- a/src/game_pages/list/index.module.scss +++ b/src/game_pages/list/index.module.scss @@ -1,8 +1,4 @@ -:global { - .guide-bar { - z-index: 999; - } -} +// GuideBar 的 z-index 通过局部样式类动态控制 .cqContainer { display: flex; @@ -89,7 +85,7 @@ .fixedHeader { position: sticky; top: 0; - z-index: 90; + z-index: 100; display: flex; flex-direction: column; } @@ -187,6 +183,11 @@ /* 隐藏时不响应鼠标事件 */ } -.guideBarList { - z-index: 999; +// GuideBar 动态 z-index 控制 +.guideBarLowZIndex { + z-index: 80 !important; // 筛选弹出时,降低层级,避免遮挡筛选内容 +} + +.guideBarHighZIndex { + z-index: 900 !important; // 正常状态,保持较高层级 } \ No newline at end of file diff --git a/src/game_pages/list/index.tsx b/src/game_pages/list/index.tsx index a16864c..7b3aff5 100644 --- a/src/game_pages/list/index.tsx +++ b/src/game_pages/list/index.tsx @@ -71,6 +71,35 @@ const ListPage = () => { const scrollStartPositionRef = useRef(0); // 记录开始滚动的位置 const [showSearchBar, setShowSearchBar] = useState(true); // 控制搜索框显示/隐藏(筛选始终显示) + // 动态控制 GuideBar 的 z-index + const [guideBarZIndex, setGuideBarZIndex] = useState<'low' | 'high'>('high'); + const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false); + const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false); + + // 处理 PublishMenu 显示/隐藏 + const handlePublishMenuVisibleChange = useCallback((visible: boolean) => { + setIsPublishMenuVisible(visible); + }, []); + + // 处理 DistanceQuickFilter 显示/隐藏 + const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => { + setIsDistanceFilterVisible(visible); + }, []); + + // 监听筛选相关组件和发布菜单的状态,动态调整 GuideBar 的 z-index + useEffect(() => { + if (isPublishMenuVisible) { + // PublishMenu 展开时,GuideBar 保持高层级 + setGuideBarZIndex('high'); + } else if (isShowFilterPopup || isDistanceFilterVisible) { + // 任何筛选组件展开时,GuideBar 降低层级 + setGuideBarZIndex('low'); + } else { + // 都关闭时,GuideBar 保持高层级 + setGuideBarZIndex('high'); + } + }, [isShowFilterPopup, isPublishMenuVisible, isDistanceFilterVisible]); + // ScrollView 滚动处理函数 const handleScrollViewScroll = useCallback( (e: any) => { @@ -442,6 +471,7 @@ const ListPage = () => { quickName="order" cityValue={distanceQuickFilter?.distanceFilter} quickValue={distanceQuickFilter?.order} + onMenuVisibleChange={handleDistanceFilterVisibleChange} /> @@ -486,7 +516,11 @@ const ListPage = () => { )} - + ); };