From 8b3f6c5a3aaae82a86053a44e9d6c69d36508e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Sun, 23 Nov 2025 00:24:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A8=8B=E5=BA=8F=E9=80=89?= =?UTF-8?q?=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEBUG_GUIDEBAR.md | 204 ++++++++++ GUIDEBAR_UNIFIED_CONTROL.md | 379 ++++++++++++++++++ src/components/CityFilter/index.tsx | 9 +- src/components/Comments/index.tsx | 13 + src/components/DistanceQuickFilter/index.tsx | 12 +- .../DistanceQuickFilterV2/index.scss | 7 +- .../DistanceQuickFilterV2/index.tsx | 18 +- src/components/GuideBar/README.md | 169 ++++++++ src/components/GuideBar/usage-example.tsx | 203 ++++++++++ src/components/HomeNavbar/index.tsx | 83 ++-- .../LocationConfirmDialog/index.scss | 122 ++++++ .../LocationConfirmDialog/index.tsx | 57 +++ src/components/UserInfo/index.tsx | 23 +- src/main_pages/index.scss | 1 + src/main_pages/index.tsx | 8 +- src/other_pages/comment_reply/index.tsx | 2 +- src/scss/common.scss | 5 + src/store/global.ts | 28 ++ 18 files changed, 1296 insertions(+), 47 deletions(-) create mode 100644 DEBUG_GUIDEBAR.md create mode 100644 GUIDEBAR_UNIFIED_CONTROL.md create mode 100644 src/components/GuideBar/README.md create mode 100644 src/components/GuideBar/usage-example.tsx create mode 100644 src/components/LocationConfirmDialog/index.scss create mode 100644 src/components/LocationConfirmDialog/index.tsx diff --git a/DEBUG_GUIDEBAR.md b/DEBUG_GUIDEBAR.md new file mode 100644 index 0000000..4f3006b --- /dev/null +++ b/DEBUG_GUIDEBAR.md @@ -0,0 +1,204 @@ +# GuideBar 隐藏问题调试指南 + +## 当前实现 + +### 1. Store 配置 (`src/store/global.ts`) +```typescript +// GuideBar 状态 +showGuideBar: true, +guideBarZIndex: 'high', + +// 控制方法 +setShowGuideBar: (show: boolean) => { + console.log('[Store] setShowGuideBar called with:', show); + set({ showGuideBar: show }); + console.log('[Store] showGuideBar updated to:', show); +}, +``` + +### 2. HomeNavbar 使用 (`src/components/HomeNavbar/index.tsx`) +```typescript +const { getLocationLoading, statusNavbarHeightInfo, setShowGuideBar } = useGlobalState(); + +const showLocationConfirmDialog = (detectedProvince: string, cachedCity: [string, string]) => { + console.log('[LocationDialog] 准备显示定位确认弹窗,隐藏 GuideBar'); + setLocationDialogData({ detectedProvince, cachedCity }); + setLocationDialogVisible(true); + setShowGuideBar(false); // 隐藏 GuideBar + console.log('[LocationDialog] setShowGuideBar(false) 已调用'); +}; +``` + +### 3. MainPage 渲染 (`src/main_pages/index.tsx`) +```typescript +const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } = useGlobalState(); + +// 渲染逻辑 +{ + showGuideBar ? + : + null +} +``` + +## 调试步骤 + +### 步骤 1: 检查控制台日志 +当 `LocationConfirmDialog` 弹窗显示时,应该能看到以下日志: +``` +[LocationDialog] 准备显示定位确认弹窗,隐藏 GuideBar +[Store] setShowGuideBar called with: false +[Store] showGuideBar updated to: false +[LocationDialog] setShowGuideBar(false) 已调用 +``` + +### 步骤 2: 检查是否有其他组件覆盖状态 +搜索以下可能的干扰: +- `UserInfo` 组件通过 `FamilyContext` 调用 `handleGrandchildTrigger` +- 其他组件可能直接调用 `setShowGuideBar(true)` + +### 步骤 3: 使用 React DevTools 检查状态 +1. 打开 React DevTools +2. 找到 `MainPage` 组件 +3. 查看 `showGuideBar` 的实时值 +4. 当弹窗显示时,检查值是否变为 `false` + +### 步骤 4: 检查 CSS 样式 +如果状态正确但 GuideBar 仍然可见,可能是 CSS 问题: +```scss +// 检查 GuideBar 的样式是否有 !important 覆盖 +.guide-bar-container { + display: block; // 不应该有 !important +} +``` + +## 可能的问题原因 + +### 原因 1: UserInfo 组件干扰 +`src/components/UserInfo/index.tsx` 使用 `FamilyContext` 控制 GuideBar: +```typescript +const { handleGrandchildTrigger } = useContext(FamilyContext); + +// 可能在某些情况下调用 +handleGrandchildTrigger(false); // 这会设置 setShowGuideBar(true) +``` + +**解决方案**:检查 `UserInfo` 组件是否在 `LocationConfirmDialog` 显示时被触发。 + +### 原因 2: useEffect 依赖问题 +`HomeNavbar` 中的 `useEffect` 可能在状态更新后重新运行: +```typescript +useEffect(() => { + // 检查这里的逻辑 +}, [locationProvince]); +``` + +**解决方案**:确保 `useEffect` 不会在弹窗显示后重置状态。 + +### 原因 3: 异步状态更新 +Zustand 的状态更新应该是同步的,但如果有异步操作可能延迟: +```typescript +setShowGuideBar(false); +// 如果这里有异步操作,可能导致状态被覆盖 +await someAsyncOperation(); +``` + +**解决方案**:确保 `setShowGuideBar(false)` 之后没有异步操作重置状态。 + +## 测试方法 + +### 方法 1: 手动测试 +1. 清除缓存:`Taro.clearStorageSync()` +2. 重新打开小程序 +3. 等待定位完成 +4. 观察是否弹出 `LocationConfirmDialog` +5. 检查 GuideBar 是否隐藏 + +### 方法 2: 代码测试 +在 `HomeNavbar` 中添加测试按钮: +```tsx + +``` + +### 方法 3: 添加更多日志 +在 `MainPage` 中监听 `showGuideBar` 变化: +```tsx +useEffect(() => { + console.log('[MainPage] showGuideBar 状态变化:', showGuideBar); +}, [showGuideBar]); +``` + +## 验证清单 + +- [ ] 控制台显示正确的日志 +- [ ] React DevTools 显示 `showGuideBar` 为 `false` +- [ ] GuideBar 组件不在 DOM 中渲染(使用元素审查工具检查) +- [ ] 没有其他组件调用 `setShowGuideBar(true)` +- [ ] CSS 样式没有强制显示 GuideBar + +## 紧急修复方案 + +如果以上都无法解决,可以尝试以下紧急方案: + +### 方案 1: 直接在 LocationConfirmDialog 中控制 +```tsx +// src/components/LocationConfirmDialog/index.tsx +import { useGlobalState } from "@/store/global"; + +const LocationConfirmDialog = (props) => { + const { setShowGuideBar } = useGlobalState(); + + useEffect(() => { + if (visible) { + setShowGuideBar(false); + } + return () => { + setShowGuideBar(true); + }; + }, [visible, setShowGuideBar]); + + // ... +}; +``` + +### 方案 2: 使用 CSS 强制隐藏 +```scss +// 当弹窗显示时,添加 class 隐藏 GuideBar +.location-dialog-visible { + .guide-bar-container { + display: none !important; + } +} +``` + +### 方案 3: 添加延迟 +```typescript +const showLocationConfirmDialog = (detectedProvince: string, cachedCity: [string, string]) => { + setLocationDialogData({ detectedProvince, cachedCity }); + setLocationDialogVisible(true); + + // 添加微小延迟确保状态更新 + setTimeout(() => { + setShowGuideBar(false); + }, 0); +}; +``` + +## 联系支持 + +如果问题仍然存在,请提供: +1. 完整的控制台日志 +2. React DevTools 截图 +3. 复现步骤 +4. 小程序版本和环境信息 + diff --git a/GUIDEBAR_UNIFIED_CONTROL.md b/GUIDEBAR_UNIFIED_CONTROL.md new file mode 100644 index 0000000..0a128b0 --- /dev/null +++ b/GUIDEBAR_UNIFIED_CONTROL.md @@ -0,0 +1,379 @@ +# GuideBar 统一控制实现总结 + +## ✅ 已完成的统一改造 + +所有页面和组件现在都使用 **`global store`** 统一管理 `GuideBar` 的显示与隐藏。 + +--- + +## 📦 核心实现 + +### 1. **Global Store** (`src/store/global.ts`) + +```typescript +interface GlobalState { + showGuideBar: boolean; + guideBarZIndex: 'low' | 'high'; +} + +interface GlobalActions { + setShowGuideBar: (show: boolean) => void; + setGuideBarZIndex: (zIndex: 'low' | 'high') => void; + toggleGuideBar: () => void; +} + +// 使用方法 +import { useGlobalState } from "@/store/global"; + +const { showGuideBar, setShowGuideBar } = useGlobalState(); +``` + +--- + +## 🎯 已改造的组件 + +### 1️⃣ **LocationConfirmDialog** - 定位确认弹窗 +**文件:** `src/components/LocationConfirmDialog/index.tsx` + +**改造内容:** +- 使用 `useEffect` 监听 `visible` 状态 +- `visible = true` 时自动隐藏 GuideBar +- `visible = false` 时自动显示 GuideBar + +```typescript +const { setShowGuideBar } = useGlobalState(); + +useEffect(() => { + if (visible) { + setShowGuideBar(false); // 弹窗显示时隐藏 + } else { + setShowGuideBar(true); // 弹窗关闭时显示 + } +}, [visible, setShowGuideBar]); +``` + +**使用场景:** +- 用户首次打开应用时的定位确认 +- 检测到位置变化时的切换提示 + +--- + +### 2️⃣ **HomeNavbar** - 首页导航栏 +**文件:** `src/components/HomeNavbar/index.tsx` + +**改造内容:** +- 引入 `setShowGuideBar` 方法 +- 在显示定位弹窗时调用隐藏 +- 在确认/取消时调用显示 + +```typescript +const { setShowGuideBar } = useGlobalState(); + +const showLocationConfirmDialog = () => { + setShowGuideBar(false); + // ... +}; + +const handleLocationDialogConfirm = () => { + // ... + setShowGuideBar(true); +}; + +const handleLocationDialogCancel = () => { + // ... + setShowGuideBar(true); +}; +``` + +**使用场景:** +- 城市切换提示 +- 定位权限请求 + +--- + +### 3️⃣ **UserInfo** - 用户信息组件(✨ 重要改造) +**文件:** `src/components/UserInfo/index.tsx` + +**改造内容:** +- ❌ 移除 `FamilyContext` 依赖 +- ✅ 改用 `useGlobalState` 统一管理 +- 优化了所有选择器(性别、地区、NTRP、职业)的 GuideBar 控制逻辑 + +**之前(使用 Context):** +```typescript +const { handleGrandchildTrigger } = useContext(FamilyContext); +handleGrandchildTrigger(true); // 隐藏(逻辑反转) +handleGrandchildTrigger(false); // 显示 +``` + +**现在(使用 Store):** +```typescript +const { setShowGuideBar } = useGlobalState(); +setShowGuideBar(false); // 隐藏(直观明了) +setShowGuideBar(true); // 显示 +``` + +**具体改动:** + +1. **打开编辑弹窗时隐藏 GuideBar** +```typescript +const handle_open_edit_modal = (field: string) => { + setShowGuideBar(false); // 之前: handleGrandchildTrigger(true) + // ... +}; +``` + +2. **关闭编辑弹窗时显示 GuideBar** +```typescript +const handle_edit_modal_cancel = () => { + setShowGuideBar(true); // 之前: handleGrandchildTrigger(false) + // ... +}; +``` + +3. **选择器状态联动** +```typescript +useEffect(() => { + const visibles = [ + gender_picker_visible, + location_picker_visible, + ntrp_picker_visible, + occupation_picker_visible, + ]; + const allPickersClosed = visibles.every((item) => !item); + // 所有选择器都关闭时显示 GuideBar,否则隐藏 + setShowGuideBar(allPickersClosed); +}, [ + gender_picker_visible, + location_picker_visible, + ntrp_picker_visible, + occupation_picker_visible, +]); +``` + +**使用场景:** +- "我的"页面编辑个人信息 +- 性别选择器 +- 地区选择器 +- NTRP 等级选择器 +- 职业选择器 +- 昵称/简介编辑 + +--- + +### 4️⃣ **MainPage** - 主页面容器 +**文件:** `src/main_pages/index.tsx` + +**改造内容:** +- 从 store 获取 `showGuideBar` 和 `setShowGuideBar` +- 保留 `handleGrandchildTrigger` 以保持向后兼容(但已无实际使用) + +```typescript +const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } = useGlobalState(); + +// 根据状态渲染 GuideBar +{ + showGuideBar ? + : + null +} +``` + +--- + +## 🔄 工作流程 + +### 示例:用户编辑个人信息 + +1. **用户点击"编辑"按钮** + ``` + handle_open_edit_modal() 调用 + → setShowGuideBar(false) + → GuideBar 隐藏 + ``` + +2. **用户打开性别选择器** + ``` + setGenderPickerVisible(true) + → useEffect 检测到 visibles 变化 + → setShowGuideBar(false) + → GuideBar 保持隐藏 + ``` + +3. **用户关闭选择器** + ``` + setGenderPickerVisible(false) + → useEffect 检测到所有选择器都关闭 + → setShowGuideBar(true) + → GuideBar 显示 + ``` + +4. **用户点击"取消"** + ``` + handle_edit_modal_cancel() 调用 + → setShowGuideBar(true) + → GuideBar 显示 + ``` + +--- + +## 📊 对比表 + +| 项目 | 之前(FamilyContext) | 现在(Global Store) | +|------|---------------------|---------------------| +| **状态管理** | Context API(跨层传递) | Zustand Store(全局统一) | +| **调用方式** | `handleGrandchildTrigger(value)` | `setShowGuideBar(show)` | +| **逻辑清晰度** | ❌ 反转逻辑(true=隐藏) | ✅ 直观逻辑(false=隐藏) | +| **依赖关系** | ❌ 需要 Context Provider | ✅ 直接引入 hook | +| **类型安全** | ⚠️ `any` 类型 | ✅ 完整 TypeScript 类型 | +| **调试能力** | ❌ 难以追踪状态变化 | ✅ 集中日志,易于调试 | +| **可维护性** | ⚠️ 分散在多处 | ✅ 统一管理 | +| **性能** | ⚠️ Context 更新触发重渲染 | ✅ Zustand 精确订阅 | + +--- + +## 🎨 优势 + +### 1. **代码更简洁** +```typescript +// 之前 +const { handleGrandchildTrigger } = useContext(FamilyContext); +handleGrandchildTrigger(true); // 反直觉 + +// 现在 +const { setShowGuideBar } = useGlobalState(); +setShowGuideBar(false); // 直观明了 +``` + +### 2. **逻辑更清晰** +- `setShowGuideBar(true)` → 显示 GuideBar +- `setShowGuideBar(false)` → 隐藏 GuideBar +- 不需要记忆反转逻辑 + +### 3. **调试更方便** +所有状态变化都有日志: +``` +[UserInfo] 打开编辑弹窗,隐藏 GuideBar +[Store] setShowGuideBar called with: false +[Store] showGuideBar updated to: false +``` + +### 4. **类型安全** +```typescript +// 完整的 TypeScript 类型定义 +interface GlobalActions { + setShowGuideBar: (show: boolean) => void; // ✅ 明确的参数类型 + toggleGuideBar: () => void; +} +``` + +### 5. **易于扩展** +需要新功能时,只需在 store 中添加: +```typescript +// 未来可以轻松添加更多控制方法 +interface GlobalActions { + setShowGuideBar: (show: boolean) => void; + setGuideBarZIndex: (zIndex: 'low' | 'high') => void; + toggleGuideBar: () => void; + hideGuideBarTemporarily: (duration: number) => void; // 新功能 +} +``` + +--- + +## 📝 使用指南 + +### 在任何组件中使用 + +```typescript +import { useGlobalState } from "@/store/global"; + +function YourComponent() { + const { showGuideBar, setShowGuideBar, toggleGuideBar } = useGlobalState(); + + // 隐藏 GuideBar + const hideGuideBar = () => setShowGuideBar(false); + + // 显示 GuideBar + const showGuideBar = () => setShowGuideBar(true); + + // 切换显示/隐藏 + const toggle = () => toggleGuideBar(); + + return ...; +} +``` + +### 自动控制(推荐) + +使用 `useEffect` 监听状态变化: +```typescript +useEffect(() => { + if (modalVisible) { + setShowGuideBar(false); + } else { + setShowGuideBar(true); + } +}, [modalVisible, setShowGuideBar]); +``` + +--- + +## ✅ 测试清单 + +### 定位弹窗测试 +- [ ] 首次打开应用时,定位弹窗显示,GuideBar 隐藏 +- [ ] 点击"切换到XX",弹窗关闭,GuideBar 显示 +- [ ] 点击"继续浏览XX",弹窗关闭,GuideBar 显示 + +### 用户信息编辑测试 +- [ ] 点击"编辑"按钮,编辑弹窗显示,GuideBar 隐藏 +- [ ] 打开性别选择器,GuideBar 保持隐藏 +- [ ] 关闭性别选择器,GuideBar 显示 +- [ ] 打开地区选择器,GuideBar 隐藏 +- [ ] 关闭地区选择器,GuideBar 显示 +- [ ] 打开 NTRP 选择器,GuideBar 隐藏 +- [ ] 关闭 NTRP 选择器,GuideBar 显示 +- [ ] 点击"取消",编辑弹窗关闭,GuideBar 显示 + +### 多选择器联动测试 +- [ ] 同时打开多个选择器时,GuideBar 保持隐藏 +- [ ] 只有所有选择器都关闭时,GuideBar 才显示 + +--- + +## 🔍 调试方法 + +### 查看控制台日志 +``` +[UserInfo] 打开编辑弹窗,隐藏 GuideBar +[Store] setShowGuideBar called with: false +[Store] showGuideBar updated to: false + +[UserInfo] 关闭编辑弹窗,显示 GuideBar +[Store] setShowGuideBar called with: true +[Store] showGuideBar updated to: true +``` + +### React DevTools +1. 打开 React DevTools +2. 找到 `MainPage` 组件 +3. 查看 `showGuideBar` 的实时值 +4. 观察状态变化是否符合预期 + +--- + +## 🎉 总结 + +✅ **LocationConfirmDialog** - 使用 store 统一控制 +✅ **HomeNavbar** - 使用 store 统一控制 +✅ **UserInfo** - 已从 FamilyContext 迁移到 store +✅ **MainPage** - 使用 store 统一渲染 + +所有组件现在都通过 **`useGlobalState`** 统一管理 GuideBar,代码更简洁、逻辑更清晰、维护更容易!🚀 + diff --git a/src/components/CityFilter/index.tsx b/src/components/CityFilter/index.tsx index 5af6117..3f79a58 100644 --- a/src/components/CityFilter/index.tsx +++ b/src/components/CityFilter/index.tsx @@ -4,7 +4,8 @@ import { useState, useRef } from "react"; import Bubble from "../Bubble"; import { Image } from "@tarojs/components"; import img from "../../config/images"; -import {DistanceFilterProps} from '../../../types/list/types' +import {DistanceFilterProps} from '../../../types/list/types'; +import { useListState } from "@/store/listStore"; @@ -15,6 +16,10 @@ const MenuComponent = (props: DistanceFilterProps) => { const [iOpen, setIsOpen] = useState(false); const itemRef = useRef(null); + // 从 store 获取当前城市信息 + const { area } = useListState(); + const currentCity = area?.at(-1) || ""; // 获取省份/城市名称 + const handleChange = (name: string, value: string) => { setIsChange(true); onChange && onChange(name, value); @@ -59,7 +64,7 @@ const MenuComponent = (props: DistanceFilterProps) => { >

当前位置

-

上海市

+

{currentCity}

{ @@ -21,6 +22,10 @@ const DistanceQuickFilter = (props) => { const [changePosition, setChangePosition] = useState([]); const [isMenuOpen, setIsMenuOpen] = useState(false); + // 从 store 获取当前城市信息 + const { area } = useListState(); + const currentCity = area?.at(-1) || ""; // 获取省份/城市名称 + // 全城筛选显示的标题 const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label; @@ -69,13 +74,16 @@ const DistanceQuickFilter = (props) => { >

当前位置

-

上海市

+

{currentCity}

handleChange(name, value, 0)} + onChange={(name, value) => { + const singleValue = Array.isArray(value) ? value[0] : value; + handleChange(name, singleValue, 0); + }} layout="grid" size="small" columns={4} diff --git a/src/components/DistanceQuickFilterV2/index.scss b/src/components/DistanceQuickFilterV2/index.scss index 9a20ec9..3dfcea5 100644 --- a/src/components/DistanceQuickFilterV2/index.scss +++ b/src/components/DistanceQuickFilterV2/index.scss @@ -19,9 +19,7 @@ background-color: #fafafa !important; z-index: 1100 !important; box-sizing: border-box !important; - max-height: auto !important; - height: 380px !important; - + max-height: 100vh !important; } .nut-menu-container-content { @@ -30,7 +28,7 @@ padding-right: 0px !important; padding-bottom: 0px !important; background-color: transparent !important; - max-height: auto !important; + max-height: 100vh !important; } @@ -121,6 +119,7 @@ padding-top: 0; display: flex; flex-direction: column; + height: 280px; .districtContent { display: flex; diff --git a/src/components/DistanceQuickFilterV2/index.tsx b/src/components/DistanceQuickFilterV2/index.tsx index 05fb0d9..1b54fc3 100644 --- a/src/components/DistanceQuickFilterV2/index.tsx +++ b/src/components/DistanceQuickFilterV2/index.tsx @@ -3,6 +3,7 @@ import { Menu } from "@nutui/nutui-react-taro"; import { Image, View, ScrollView } from "@tarojs/components"; import img from "@/config/images"; import Bubble from "../Bubble"; +import { useListState } from "@/store/listStore"; import "./index.scss"; const DistanceQuickFilterV2 = (props) => { @@ -24,6 +25,10 @@ const DistanceQuickFilterV2 = (props) => { const [changePosition, setChangePosition] = useState([]); const [isMenuOpen, setIsMenuOpen] = useState(false); + // 从 store 获取当前城市信息 + const { area } = useListState(); + const currentCity = area?.at(-1) || ""; // 获取省份/城市名称 + // 全城筛选显示的标题 - 如果选择了行政区,显示行政区名称 const getCityTitle = () => { if (districtValue) { @@ -56,6 +61,17 @@ const DistanceQuickFilterV2 = (props) => { const newData = new Set([...preState, index]); return Array.from(newData); }); + + // 处理互斥关系:距离筛选和行政区完全互斥 + if (name === cityName) { + // 选择了距离筛选(包括"全城"),清空行政区选择 + onChange && onChange(districtName, ""); + } else if (name === districtName) { + // 选择了行政区,将距离重置为"全城"(空字符串) + onChange && onChange(cityName, ""); + } + + // 触发当前选择的变化 onChange && onChange(name, value); // 控制隐藏 @@ -82,7 +98,7 @@ const DistanceQuickFilterV2 = (props) => { >

当前位置

-

上海市

+

{currentCity}

+ {showGuideBar ? GuideBar 已显示 : GuideBar 已隐藏} + + ); +} +``` + +### 2. 控制 GuideBar 显示/隐藏 + +```tsx +import { useGlobalState } from "@/store/global"; + +function YourComponent() { + const { setShowGuideBar, toggleGuideBar } = useGlobalState(); + + const handleHideGuideBar = () => { + setShowGuideBar(false); // 隐藏 GuideBar + }; + + const handleShowGuideBar = () => { + setShowGuideBar(true); // 显示 GuideBar + }; + + const handleToggle = () => { + toggleGuideBar(); // 切换显示/隐藏状态 + }; + + return ( + + + + + + ); +} +``` + +### 3. 控制 GuideBar z-index + +```tsx +import { useGlobalState } from "@/store/global"; + +function YourComponent() { + const { setGuideBarZIndex } = useGlobalState(); + + const handleOpenModal = () => { + // 打开弹窗时,降低 GuideBar 层级 + setGuideBarZIndex('low'); + }; + + const handleCloseModal = () => { + // 关闭弹窗时,恢复 GuideBar 层级 + setGuideBarZIndex('high'); + }; + + return ( + + + + ); +} +``` + +### 4. 完整示例:视频播放器 + +```tsx +import { useGlobalState } from "@/store/global"; +import { useEffect } from "react"; + +function VideoPlayer() { + const { setShowGuideBar } = useGlobalState(); + + // 进入全屏时隐藏 GuideBar + const handleFullscreen = () => { + setShowGuideBar(false); + }; + + // 退出全屏时显示 GuideBar + const handleExitFullscreen = () => { + setShowGuideBar(true); + }; + + // 组件卸载时恢复 GuideBar + useEffect(() => { + return () => { + setShowGuideBar(true); + }; + }, [setShowGuideBar]); + + return ( + + + ); +} +``` + +## API 说明 + +### 状态 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `showGuideBar` | `boolean` | GuideBar 是否显示 | +| `guideBarZIndex` | `'low' \| 'high'` | GuideBar 的层级,用于处理与弹窗的层级关系 | + +### 方法 + +| 方法 | 参数 | 说明 | +|------|------|------| +| `setShowGuideBar` | `(show: boolean) => void` | 设置 GuideBar 显示/隐藏 | +| `setGuideBarZIndex` | `(zIndex: 'low' \| 'high') => void` | 设置 GuideBar z-index 层级 | +| `toggleGuideBar` | `() => void` | 切换 GuideBar 显示/隐藏状态 | + +## 使用场景 + +### 1. 全屏播放/浏览 +当用户进入全屏模式(如视频播放、图片预览)时,应该隐藏 GuideBar: + +```tsx +const handleEnterFullscreen = () => { + setShowGuideBar(false); +}; +``` + +### 2. 弹窗/遮罩层 +当页面显示弹窗或遮罩层时,需要调整 GuideBar 的层级: + +```tsx +const handleShowModal = () => { + setGuideBarZIndex('low'); // 让弹窗在 GuideBar 之上 +}; +``` + +### 3. 沉浸式阅读 +在某些需要沉浸式体验的页面(如长文阅读、详情页滚动): + +```tsx +const handleScroll = (e) => { + if (e.detail.scrollTop > 200) { + setShowGuideBar(false); // 滚动到一定距离后隐藏 + } else { + setShowGuideBar(true); + } +}; +``` + +## 注意事项 + +1. **恢复状态**:在组件卸载时,记得恢复 GuideBar 的状态,避免影响其他页面 +2. **层级管理**:`guideBarZIndex` 通常由主页面自动管理,除非有特殊需求,否则不建议手动设置 +3. **性能优化**:频繁切换显示/隐藏可能影响性能,建议使用防抖或节流 + diff --git a/src/components/GuideBar/usage-example.tsx b/src/components/GuideBar/usage-example.tsx new file mode 100644 index 0000000..9cc0ce5 --- /dev/null +++ b/src/components/GuideBar/usage-example.tsx @@ -0,0 +1,203 @@ +/** + * GuideBar Store 使用示例 + * + * 这个文件展示了如何在不同场景下使用 GuideBar 的 store + */ + +import { useGlobalState } from "@/store/global"; +import { useEffect } from "react"; +import { View, Button } from "@tarojs/components"; + +// ============ 示例 1: 视频播放器组件 ============ +export function VideoPlayerExample() { + const { setShowGuideBar } = useGlobalState(); + + // 进入全屏 + const handleFullscreen = () => { + setShowGuideBar(false); + }; + + // 退出全屏 + const handleExitFullscreen = () => { + setShowGuideBar(true); + }; + + // 组件卸载时恢复 + useEffect(() => { + return () => { + setShowGuideBar(true); + }; + }, [setShowGuideBar]); + + return ( + + + + + ); +} + +// ============ 示例 2: 弹窗组件 ============ +export function ModalExample() { + const { setGuideBarZIndex } = useGlobalState(); + + const handleShowModal = () => { + // 显示弹窗时,降低 GuideBar 层级 + setGuideBarZIndex('low'); + }; + + const handleCloseModal = () => { + // 关闭弹窗时,恢复 GuideBar 层级 + setGuideBarZIndex('high'); + }; + + return ( + + + + ); +} + +// ============ 示例 3: 沉浸式滚动页面 ============ +export function ImmersiveScrollExample() { + const { showGuideBar, setShowGuideBar } = useGlobalState(); + + const handleScroll = (e: any) => { + const scrollTop = e.detail.scrollTop; + + // 向下滚动超过 200px 时隐藏 GuideBar + if (scrollTop > 200 && showGuideBar) { + setShowGuideBar(false); + } + // 向上滚动回到顶部时显示 GuideBar + else if (scrollTop <= 200 && !showGuideBar) { + setShowGuideBar(true); + } + }; + + return ( + + {/* 页面内容 */} + + ); +} + +// ============ 示例 4: 图片预览器 ============ +export function ImagePreviewExample() { + const { setShowGuideBar } = useGlobalState(); + + const handlePreviewImage = () => { + // 预览图片时隐藏 GuideBar + setShowGuideBar(false); + }; + + const handleClosePreview = () => { + // 关闭预览时显示 GuideBar + setShowGuideBar(true); + }; + + useEffect(() => { + return () => { + // 组件卸载时恢复 GuideBar + setShowGuideBar(true); + }; + }, [setShowGuideBar]); + + return ( + + + + ); +} + +// ============ 示例 5: 切换开关 ============ +export function ToggleExample() { + const { showGuideBar, toggleGuideBar } = useGlobalState(); + + return ( + + + + ); +} + +// ============ 示例 6: 游戏/互动页面 ============ +export function GamePageExample() { + const { setShowGuideBar } = useGlobalState(); + + // 游戏开始时隐藏 GuideBar + const handleStartGame = () => { + setShowGuideBar(false); + }; + + // 游戏结束时显示 GuideBar + const handleGameOver = () => { + setShowGuideBar(true); + }; + + // 页面显示时隐藏,离开时恢复 + useEffect(() => { + setShowGuideBar(false); + + return () => { + setShowGuideBar(true); + }; + }, [setShowGuideBar]); + + return ( + + + + + ); +} + +// ============ 示例 7: 详情页(根据内容动态显示) ============ +export function DetailPageExample() { + const { setShowGuideBar } = useGlobalState(); + + useEffect(() => { + // 进入详情页时根据内容长度决定是否显示 GuideBar + const contentHeight = 1500; // 假设内容高度 + const screenHeight = 800; // 假设屏幕高度 + + if (contentHeight > screenHeight * 2) { + // 内容很长时,初始隐藏 GuideBar,提供更好的阅读体验 + setShowGuideBar(false); + } + + return () => { + setShowGuideBar(true); + }; + }, [setShowGuideBar]); + + return ( + + {/* 详情内容 */} + + ); +} + +// ============ 示例 8: 表单页面(避免键盘遮挡) ============ +export function FormPageExample() { + const { setShowGuideBar } = useGlobalState(); + + // 输入框聚焦时隐藏 GuideBar(避免键盘遮挡) + const handleFocus = () => { + setShowGuideBar(false); + }; + + // 输入框失焦时显示 GuideBar + const handleBlur = () => { + setShowGuideBar(true); + }; + + return ( + + + + ); +} + diff --git a/src/components/HomeNavbar/index.tsx b/src/components/HomeNavbar/index.tsx index b69d592..314436f 100644 --- a/src/components/HomeNavbar/index.tsx +++ b/src/components/HomeNavbar/index.tsx @@ -9,6 +9,7 @@ import Taro from "@tarojs/taro"; import "./index.scss"; import { getCurrentFullPath } from "@/utils"; import { CityPickerV2 as PopupPicker } from "@/components/Picker"; +import LocationConfirmDialog from "@/components/LocationConfirmDialog"; // 城市缓存 key const CITY_CACHE_KEY = "USER_SELECTED_CITY"; @@ -70,7 +71,7 @@ const HomeNavbar = (props: IProps) => { title, showTitle = false, } = config || {}; - const { getLocationLoading, statusNavbarHeightInfo } = useGlobalState(); + const { getLocationLoading, statusNavbarHeightInfo, setShowGuideBar } = useGlobalState(); const { gamesNum, searchValue, @@ -84,6 +85,11 @@ const HomeNavbar = (props: IProps) => { statusNavbarHeightInfo || {}; const [cityPopupVisible, setCityPopupVisible] = useState(false); + const [locationDialogVisible, setLocationDialogVisible] = useState(false); + const [locationDialogData, setLocationDialogData] = useState<{ + detectedProvince: string; + cachedCity: [string, string]; + } | null>(null); const hasShownLocationDialog = useRef(false); // 防止重复弹窗 // 监听城市选择器状态变化,通知父组件 @@ -122,28 +128,49 @@ const HomeNavbar = (props: IProps) => { // 显示定位确认弹窗 const showLocationConfirmDialog = (detectedProvince: string, cachedCity: [string, string]) => { - (Taro as any).showModal({ - title: "提示", - content: `检测到您当前位置在${detectedProvince},是否切换到${detectedProvince}?`, - confirmText: "是", - cancelText: "否", - success: (res) => { - if (res.confirm) { - // 用户选择"是",切换到当前定位城市 - const newArea: [string, string] = ["中国", detectedProvince]; - updateArea(newArea); - // 更新缓存为新的定位信息(只有定位的才缓存) - (Taro as any).setStorageSync(CITY_CACHE_KEY, newArea); - console.log("切换到当前定位城市并更新缓存:", detectedProvince); - - // 刷新数据 - handleCityChangeWithoutCache(); - } else { - // 用户选择"否",保持缓存的定位城市 - console.log("保持缓存的定位城市:", cachedCity[1]); - } - } - }); + console.log('[LocationDialog] 准备显示定位确认弹窗,隐藏 GuideBar'); + setLocationDialogData({ detectedProvince, cachedCity }); + setLocationDialogVisible(true); + // 显示弹窗时隐藏 GuideBar + setShowGuideBar(false); + console.log('[LocationDialog] setShowGuideBar(false) 已调用'); + }; + + // 处理定位弹窗确认 + const handleLocationDialogConfirm = () => { + if (!locationDialogData) return; + + const { detectedProvince } = locationDialogData; + // 用户选择"是",切换到当前定位城市 + const newArea: [string, string] = ["中国", detectedProvince]; + updateArea(newArea); + // 更新缓存为新的定位信息(只有定位的才缓存) + (Taro as any).setStorageSync(CITY_CACHE_KEY, newArea); + console.log("切换到当前定位城市并更新缓存:", detectedProvince); + + // 关闭弹窗 + setLocationDialogVisible(false); + setLocationDialogData(null); + // 关闭弹窗时显示 GuideBar + setShowGuideBar(true); + + // 刷新数据 + handleCityChangeWithoutCache(); + }; + + // 处理定位弹窗取消 + const handleLocationDialogCancel = () => { + if (!locationDialogData) return; + + const { cachedCity } = locationDialogData; + // 用户选择"否",保持缓存的定位城市 + console.log("保持缓存的定位城市:", cachedCity[1]); + + // 关闭弹窗 + setLocationDialogVisible(false); + setLocationDialogData(null); + // 关闭弹窗时显示 GuideBar + setShowGuideBar(true); }; // const currentAddress = city + district; @@ -318,6 +345,16 @@ const HomeNavbar = (props: IProps) => { onCityChange={handleCityChange} /> )} + {/* 定位确认弹窗 */} + {locationDialogData && ( + + )} ); }; diff --git a/src/components/LocationConfirmDialog/index.scss b/src/components/LocationConfirmDialog/index.scss new file mode 100644 index 0000000..2b91270 --- /dev/null +++ b/src/components/LocationConfirmDialog/index.scss @@ -0,0 +1,122 @@ +.locationDialogMask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: flex-end; + justify-content: center; + z-index: 10000; + animation: fadeIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } +} + +.locationDialogContainer { + width: 100%; + background: transparent; + animation: slideUp 0.3s ease-out; + padding-bottom: 20px; + background: #ffffff; + border-radius: 20px 20px 0px 0px; + height: 246px; +} + +.locationDialogContent { + display: flex; + flex-direction: column; + align-items: center; + + overflow: hidden; + margin-left: 24px; + margin-right: 24px; +} + +.locationDialogTitle { + padding-top: 48px; + padding-bottom: 32px; + margin-left: 32px; + margin-right: 32px; + font-family: "PingFang SC"; + font-weight: 600; + font-size: 20px; + line-height: 28px; + text-align: center; + color: #000000; +} + +.locationDialogButtons { + display: flex; + flex-direction: column; + padding-bottom: 20px; + width: calc(100% - 40px); +} + +.locationDialogButton { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 16px; + cursor: pointer; + transition: opacity 0.2s; + margin-top: 20px; + + &:first-child { + margin-top: 0; + } + + &.primary { + background: #000000; + height: 52px; + + &:active { + opacity: 0.8; + } + } + + &.secondary { + width: 100%; + background: #ffffff; + height: 52px; + border: 1px solid rgba(0, 0, 0, 0.1); + + &:active { + background: rgba(0, 0, 0, 0.05); + } + } +} + +.locationDialogButtonText { + font-family: "PingFang SC"; + font-weight: 600; + font-size: 16px; + line-height: 22px; + text-align: center; + + &.primary { + color: #ffffff; + } + + &.secondary { + color: #000000; + } +} diff --git a/src/components/LocationConfirmDialog/index.tsx b/src/components/LocationConfirmDialog/index.tsx new file mode 100644 index 0000000..09424a8 --- /dev/null +++ b/src/components/LocationConfirmDialog/index.tsx @@ -0,0 +1,57 @@ +import React, { useEffect } from "react"; +import { View, Text } from "@tarojs/components"; +import { useGlobalState } from "@/store/global"; +import "./index.scss"; + +interface LocationConfirmDialogProps { + visible: boolean; + currentCity: string; // 缓存的城市 + detectedCity: string; // 定位的城市 + onConfirm: () => void; + onCancel: () => void; +} + +const LocationConfirmDialog: React.FC = ({ + visible, + currentCity, + detectedCity, + onConfirm, + onCancel, +}) => { + const { setShowGuideBar } = useGlobalState(); + + // 当弹窗显示/隐藏时,控制 GuideBar + useEffect(() => { + console.log('[LocationConfirmDialog] visible 变化:', visible); + if (visible) { + console.log('[LocationConfirmDialog] 弹窗显示,隐藏 GuideBar'); + setShowGuideBar(false); + } else { + console.log('[LocationConfirmDialog] 弹窗隐藏,显示 GuideBar'); + setShowGuideBar(true); + } + }, [visible, setShowGuideBar]); + + if (!visible) return null; + + return ( + + e.stopPropagation()}> + + 定位显示您在{detectedCity} + + + 切换到{detectedCity} + + + 继续浏览{currentCity} + + + + + + ); +}; + +export default LocationConfirmDialog; + diff --git a/src/components/UserInfo/index.tsx b/src/components/UserInfo/index.tsx index d513b4f..a1a90a3 100644 --- a/src/components/UserInfo/index.tsx +++ b/src/components/UserInfo/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef, useContext } from "react"; +import React, { useState, useEffect, useRef } from "react"; import Taro, { useDidShow } from "@tarojs/taro"; import { View, Text, Image, Button } from "@tarojs/components"; import "./index.scss"; @@ -10,7 +10,7 @@ import { useUserActions } from "@/store/userStore"; import { UserInfoType } from "@/services/userService"; import { useCities, useProfessions } from "@/store/pickerOptionsStore"; import { formatNtrpDisplay } from "@/utils/helper"; -import FamilyContext from "@/context"; +import { useGlobalState } from "@/store/global"; // 用户信息接口 // export interface UserInfo { @@ -72,7 +72,7 @@ const UserInfoCardComponent: React.FC = ({ set_user_info, onTab, }) => { - const { handleGrandchildTrigger } = useContext(FamilyContext); + const { setShowGuideBar } = useGlobalState(); const { updateUserInfo } = useUserActions(); // 使用 useRef 记录上一次的 user_info,只在真正变化时打印 @@ -111,10 +111,9 @@ const UserInfoCardComponent: React.FC = ({ ntrp_picker_visible, occupation_picker_visible, ]; - const showGuideBar = visibles.every((item) => !item); - if (showGuideBar) { - handleGrandchildTrigger(false); - } + const allPickersClosed = visibles.every((item) => !item); + // 所有选择器都关闭时,显示 GuideBar;否则隐藏 + setShowGuideBar(allPickersClosed); }, [ gender_picker_visible, location_picker_visible, @@ -152,7 +151,8 @@ const UserInfoCardComponent: React.FC = ({ // }; // 处理编辑弹窗 const handle_open_edit_modal = (field: string) => { - handleGrandchildTrigger(true); + // 打开编辑弹窗时隐藏 GuideBar + setShowGuideBar(false); if (field === "gender") { setGenderPickerVisible(true); return; @@ -171,11 +171,11 @@ const UserInfoCardComponent: React.FC = ({ } if (field === "nickname") { // 手动输入 - handleGrandchildTrigger(true); + setShowGuideBar(false); setEditingField(field); setEditModalVisible(true); } else { - handleGrandchildTrigger(true); + setShowGuideBar(false); setEditingField(field); setEditModalVisible(true); } @@ -283,7 +283,8 @@ const UserInfoCardComponent: React.FC = ({ handle_field_edit("occupation", `${country} ${province} ${city}`); }; const handle_edit_modal_cancel = () => { - handleGrandchildTrigger(false); + // 关闭编辑弹窗时显示 GuideBar + setShowGuideBar(true); setEditModalVisible(false); setEditingField(""); }; diff --git a/src/main_pages/index.scss b/src/main_pages/index.scss index 4c2c663..2e8d1a1 100644 --- a/src/main_pages/index.scss +++ b/src/main_pages/index.scss @@ -42,6 +42,7 @@ // 隐藏所有子页面中的GuideBar(使用全局样式) .tab-content .guide-bar-container { display: none !important; + } } diff --git a/src/main_pages/index.tsx b/src/main_pages/index.tsx index 0d1ee72..2b16d19 100644 --- a/src/main_pages/index.tsx +++ b/src/main_pages/index.tsx @@ -3,6 +3,7 @@ import { View } from "@tarojs/components"; import Taro from "@tarojs/taro"; import { wechat_auth_login, save_login_state } from "@/services/loginService"; import { useUserActions } from "@/store/userStore"; +import { useGlobalState } from "@/store/global"; import tokenManager from "@/utils/tokenManager"; import GuideBar from "@/components/GuideBar"; import { GeneralNavbar } from "@/components"; @@ -18,17 +19,17 @@ type TabType = "list" | "message" | "personal"; const MainPage: React.FC = () => { const [currentTab, setCurrentTab] = useState("list"); const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false); - const [guideBarZIndex, setGuideBarZIndex] = useState<'low' | 'high'>('high'); const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false); const [isCityPickerVisible, setIsCityPickerVisible] = useState(false); const [isFilterPopupVisible, setIsFilterPopupVisible] = useState(false); const [isShowInputCustomerNavBar, setIsShowInputCustomerNavBar] = useState(false); const [listPageScrollToTopTrigger, setListPageScrollToTopTrigger] = useState(0); - const [showGuideBar, setShowGuideBar] = useState(true); const [showAuthError, setShowAuthError] = useState(false); const [authErrorMessage, setAuthErrorMessage] = useState(''); const { fetchUserInfo } = useUserActions(); + // 从 store 获取 GuideBar 相关状态和方法 + const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } = useGlobalState(); // 初始化:自动微信授权并获取用户信息 useEffect(() => { @@ -201,7 +202,8 @@ const MainPage: React.FC = () => { }; const handleGrandchildTrigger = (value) => { - setShowGuideBar(!value) + console.log('[MainPage] handleGrandchildTrigger called with:', value); + setShowGuideBar(!value); } return ( diff --git a/src/other_pages/comment_reply/index.tsx b/src/other_pages/comment_reply/index.tsx index d13bddc..d4c8b3c 100644 --- a/src/other_pages/comment_reply/index.tsx +++ b/src/other_pages/comment_reply/index.tsx @@ -73,7 +73,7 @@ const CommentReply = () => { // 获取未读评论ID并标记已读 const unreadIds = res.data.rows - .filter((item: any) => item.is_read === 0 && item.activity_type === 'received_reply') + .filter((item: any) => item.is_read === 0) .map((item: any) => item.id); if (unreadIds.length > 0) { diff --git a/src/scss/common.scss b/src/scss/common.scss index 7ec236e..db0c88b 100644 --- a/src/scss/common.scss +++ b/src/scss/common.scss @@ -120,4 +120,9 @@ $color-background: #FAFAFA !default; border-radius: 50%; overflow: hidden; box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.2); +} + +.avatar +{ + border: none; } \ No newline at end of file diff --git a/src/store/global.ts b/src/store/global.ts index 1b8dd30..8ee788b 100644 --- a/src/store/global.ts +++ b/src/store/global.ts @@ -11,12 +11,20 @@ interface GlobalState { navBarHeight: number; totalHeight: number; }; + // GuideBar 显示/隐藏状态 + showGuideBar: boolean; + // GuideBar z-index 层级 + guideBarZIndex: 'low' | 'high'; } interface GlobalActions { updateState: (payload: Record) => void; getNavbarHeightInfo: () => void; getCurrentLocationInfo: () => any; + // GuideBar 控制方法 + setShowGuideBar: (show: boolean) => void; + setGuideBarZIndex: (zIndex: 'low' | 'high') => void; + toggleGuideBar: () => void; } // 完整的 Store 类型 type GlobalStore = GlobalState & GlobalActions; @@ -37,6 +45,10 @@ export const useGlobalStore = create()((set, get) => ({ totalHeight: 0, }, + // GuideBar 状态 + showGuideBar: true, + guideBarZIndex: 'high', + // 获取导航栏高度信息 getNavbarHeightInfo: () => { const { statusBarHeight, navBarHeight } = getNavbarHeight(); @@ -67,6 +79,22 @@ export const useGlobalStore = create()((set, get) => ({ ...(payload || {}), }); }, + + // GuideBar 控制方法 + setShowGuideBar: (show: boolean) => { + console.log('[Store] setShowGuideBar called with:', show); + set({ showGuideBar: show }); + console.log('[Store] showGuideBar updated to:', show); + }, + + setGuideBarZIndex: (zIndex: 'low' | 'high') => { + set({ guideBarZIndex: zIndex }); + }, + + toggleGuideBar: () => { + const { showGuideBar } = get(); + set({ showGuideBar: !showGuideBar }); + }, })); // 导出便捷的 hooks