修复 z-index 层级问题
This commit is contained in:
105
docs/z-index-guide.md
Normal file
105
docs/z-index-guide.md
Normal file
@@ -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,解决层级冲突问题
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
border-bottom-left-radius: 30px;
|
border-bottom-left-radius: 30px;
|
||||||
border-bottom-right-radius: 30px;
|
border-bottom-right-radius: 30px;
|
||||||
background-color: #fafafa !important;
|
background-color: #fafafa !important;
|
||||||
z-index: 1000 !important;
|
z-index: 1100 !important;
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useRef, useState } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import { Menu } from "@nutui/nutui-react-taro";
|
import { Menu } from "@nutui/nutui-react-taro";
|
||||||
import { Image, View } from "@tarojs/components";
|
import { Image, View } from "@tarojs/components";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
@@ -14,10 +14,12 @@ const DistanceQuickFilter = (props) => {
|
|||||||
quickName,
|
quickName,
|
||||||
cityValue,
|
cityValue,
|
||||||
quickValue,
|
quickValue,
|
||||||
|
onMenuVisibleChange, // 新增:菜单展开/收起回调
|
||||||
} = props;
|
} = props;
|
||||||
const cityRef = useRef(null);
|
const cityRef = useRef(null);
|
||||||
const quickRef = useRef(null);
|
const quickRef = useRef(null);
|
||||||
const [changePosition, setChangePosition] = useState<number[]>([]);
|
const [changePosition, setChangePosition] = useState<number[]>([]);
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
// 全城筛选显示的标题
|
// 全城筛选显示的标题
|
||||||
const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
|
const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
|
||||||
@@ -48,10 +50,17 @@ const DistanceQuickFilter = (props) => {
|
|||||||
index === 1 && (quickRef.current as any)?.toggle(false);
|
index === 1 && (quickRef.current as any)?.toggle(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听菜单状态变化,通知父组件
|
||||||
|
useEffect(() => {
|
||||||
|
onMenuVisibleChange?.(isMenuOpen);
|
||||||
|
}, [isMenuOpen, onMenuVisibleChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Menu
|
<Menu
|
||||||
className={`distanceQuickFilterWrap ${filterWrapperClassName}`}
|
className={`distanceQuickFilterWrap ${filterWrapperClassName}`}
|
||||||
|
onOpen={() => setIsMenuOpen(true)}
|
||||||
|
onClose={() => setIsMenuOpen(false)}
|
||||||
>
|
>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
title={cityTitle}
|
title={cityTitle}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
style={{ marginTop: statusNavbarHeigh + "px", maxHeight: '70vh' }}
|
style={{ marginTop: statusNavbarHeigh + "px", maxHeight: '70vh' }}
|
||||||
overlayStyle={{ marginTop: statusNavbarHeigh + "px" }}
|
overlayStyle={{ marginTop: statusNavbarHeigh + "px" }}
|
||||||
zIndex={1001}
|
zIndex={1200}
|
||||||
>
|
>
|
||||||
<View className={styles.filterPopupWrapper}>
|
<View className={styles.filterPopupWrapper}>
|
||||||
{/* 可滚动的内容区域 */}
|
{/* 可滚动的内容区域 */}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 12px 32px;
|
padding: 20px 12px 32px;
|
||||||
z-index: 999;
|
z-index: 900;
|
||||||
|
|
||||||
&-pages {
|
&-pages {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import PublishMenu from "../PublishMenu";
|
|||||||
export type currentPageType = "games" | "message" | "personal";
|
export type currentPageType = "games" | "message" | "personal";
|
||||||
|
|
||||||
const GuideBar = (props) => {
|
const GuideBar = (props) => {
|
||||||
const { currentPage, guideBarClassName } = props;
|
const { currentPage, guideBarClassName, onPublishMenuVisibleChange } = props;
|
||||||
|
|
||||||
const guideItems = [
|
const guideItems = [
|
||||||
{
|
{
|
||||||
@@ -73,7 +73,7 @@ const GuideBar = (props) => {
|
|||||||
{/* <View className='guide-bar-publish' onClick={handlePublish}>
|
{/* <View className='guide-bar-publish' onClick={handlePublish}>
|
||||||
<Image className='guide-bar-publish-icon' src={img.ICON_GUIDE_BAR_PUBLISH} />
|
<Image className='guide-bar-publish-icon' src={img.ICON_GUIDE_BAR_PUBLISH} />
|
||||||
</View> */}
|
</View> */}
|
||||||
<PublishMenu />
|
<PublishMenu onVisibleChange={onPublishMenuVisibleChange} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 { View, Text, Image } from "@tarojs/components";
|
||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import { useUserInfo } from "@/store/userStore";
|
import { useUserInfo } from "@/store/userStore";
|
||||||
@@ -15,10 +15,17 @@ import NTRPEvaluatePopup from "../NTRPEvaluatePopup";
|
|||||||
export interface PublishMenuProps {
|
export interface PublishMenuProps {
|
||||||
onPersonalPublish?: () => void;
|
onPersonalPublish?: () => void;
|
||||||
onActivityPublish?: () => void;
|
onActivityPublish?: () => void;
|
||||||
|
onVisibleChange?: (visible: boolean) => void; // 菜单显示/隐藏回调
|
||||||
}
|
}
|
||||||
|
|
||||||
const PublishMenu: React.FC<PublishMenuProps> = () => {
|
const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
||||||
|
const { onVisibleChange } = props;
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
// 使用 useEffect 监听 isVisible 变化,确保所有情况都能触发回调
|
||||||
|
useEffect(() => {
|
||||||
|
onVisibleChange?.(isVisible);
|
||||||
|
}, [isVisible, onVisibleChange]);
|
||||||
const [aiImportVisible, setAiImportVisible] = useState(false);
|
const [aiImportVisible, setAiImportVisible] = useState(false);
|
||||||
const userInfo = useUserInfo();
|
const userInfo = useUserInfo();
|
||||||
const ntrpRef = useRef<{
|
const ntrpRef = useRef<{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.publishMenu {
|
.publishMenu {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1000;
|
z-index: 950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 999;
|
z-index: 940;
|
||||||
}
|
}
|
||||||
.menuCard {
|
.menuCard {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
z-index: 1001;
|
z-index: 960;
|
||||||
// /* 小三角指示器 */
|
// /* 小三角指示器 */
|
||||||
// &::after {
|
// &::after {
|
||||||
// content: '';
|
// content: '';
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const ListContainer = (props) => {
|
|||||||
// 延迟 300ms 后再显示骨架屏
|
// 延迟 300ms 后再显示骨架屏
|
||||||
skeletonTimerRef.current = setTimeout(() => {
|
skeletonTimerRef.current = setTimeout(() => {
|
||||||
setShowSkeleton(true);
|
setShowSkeleton(true);
|
||||||
}, 300);
|
}, 600);
|
||||||
} else {
|
} else {
|
||||||
// 加载完成,清除定时器并隐藏骨架屏
|
// 加载完成,清除定时器并隐藏骨架屏
|
||||||
if (skeletonTimerRef.current) {
|
if (skeletonTimerRef.current) {
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
:global {
|
// GuideBar 的 z-index 通过局部样式类动态控制
|
||||||
.guide-bar {
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cqContainer {
|
.cqContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -89,7 +85,7 @@
|
|||||||
.fixedHeader {
|
.fixedHeader {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 90;
|
z-index: 100;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -187,6 +183,11 @@
|
|||||||
/* 隐藏时不响应鼠标事件 */
|
/* 隐藏时不响应鼠标事件 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.guideBarList {
|
// GuideBar 动态 z-index 控制
|
||||||
z-index: 999;
|
.guideBarLowZIndex {
|
||||||
|
z-index: 80 !important; // 筛选弹出时,降低层级,避免遮挡筛选内容
|
||||||
|
}
|
||||||
|
|
||||||
|
.guideBarHighZIndex {
|
||||||
|
z-index: 900 !important; // 正常状态,保持较高层级
|
||||||
}
|
}
|
||||||
@@ -71,6 +71,35 @@ const ListPage = () => {
|
|||||||
const scrollStartPositionRef = useRef(0); // 记录开始滚动的位置
|
const scrollStartPositionRef = useRef(0); // 记录开始滚动的位置
|
||||||
const [showSearchBar, setShowSearchBar] = useState(true); // 控制搜索框显示/隐藏(筛选始终显示)
|
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 滚动处理函数
|
// ScrollView 滚动处理函数
|
||||||
const handleScrollViewScroll = useCallback(
|
const handleScrollViewScroll = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
@@ -442,6 +471,7 @@ const ListPage = () => {
|
|||||||
quickName="order"
|
quickName="order"
|
||||||
cityValue={distanceQuickFilter?.distanceFilter}
|
cityValue={distanceQuickFilter?.distanceFilter}
|
||||||
quickValue={distanceQuickFilter?.order}
|
quickValue={distanceQuickFilter?.order}
|
||||||
|
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -486,7 +516,11 @@ const ListPage = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<GuideBar currentPage="list" guideBarClassName={styles.guideBarList} />
|
<GuideBar
|
||||||
|
currentPage="list"
|
||||||
|
guideBarClassName={`${styles.guideBarList} ${guideBarZIndex === 'low' ? styles.guideBarLowZIndex : styles.guideBarHighZIndex}`}
|
||||||
|
onPublishMenuVisibleChange={handlePublishMenuVisibleChange}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user