From d6349d14e8b6c1668fb27d546c17a94291a3fcdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Fri, 14 Nov 2025 22:03:09 +0800 Subject: [PATCH] 1 --- project.private.config.json | 2 +- src/app.config.ts | 4 +- src/components/GuideBar/index.tsx | 11 +- src/components/UserInfo/index.tsx | 36 +- src/home_pages/index.tsx | 4 +- src/pages/main/components/ListPageContent.tsx | 396 ++++++++++++++++++ .../main/components/MessagePageContent.tsx | 196 +++++++++ .../main/components/MyselfPageContent.tsx | 233 +++++++++++ src/pages/main/index.config.ts | 5 + src/pages/main/index.scss | 52 +++ src/pages/main/index.tsx | 191 +++++++++ 11 files changed, 1120 insertions(+), 10 deletions(-) create mode 100644 src/pages/main/components/ListPageContent.tsx create mode 100644 src/pages/main/components/MessagePageContent.tsx create mode 100644 src/pages/main/components/MyselfPageContent.tsx create mode 100644 src/pages/main/index.config.ts create mode 100644 src/pages/main/index.scss create mode 100644 src/pages/main/index.tsx diff --git a/project.private.config.json b/project.private.config.json index 8b66178..014269a 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -15,7 +15,7 @@ "useStaticServer": false, "useLanDebug": false, "showES6CompileOption": false, - "compileHotReLoad": true, + "compileHotReLoad": false, "checkInvalidKey": true, "ignoreDevUnusedFiles": true, "bigPackageSizeSupport": true diff --git a/src/app.config.ts b/src/app.config.ts index 889c52e..5a8bd73 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,11 +1,11 @@ export default defineAppConfig({ pages: [ - + "pages/main/index", // 主容器页面(合并三个tab) "home_pages/index", //中转页 "login_pages/index/index", "login_pages/verification/index", "login_pages/terms/index", - "game_pages/list/index", + "game_pages/list/index", // 保留作为独立页面(兼容性) "game_pages/search/index", // 搜索页 "game_pages/searchResult/index", // 搜索结果页面 "game_pages/detail/index", // 球局详情页 diff --git a/src/components/GuideBar/index.tsx b/src/components/GuideBar/index.tsx index 4fb476e..9915f56 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, onPublishMenuVisibleChange } = props; + const { currentPage, guideBarClassName, onPublishMenuVisibleChange, onTabChange } = props; const guideItems = [ { @@ -34,7 +34,14 @@ const GuideBar = (props) => { if (code === currentPage) { return; } + + // 如果提供了 onTabChange 回调,优先使用(单页面模式) + if (onTabChange) { + onTabChange(code); + return; + } + // 否则使用路由跳转(兼容模式) let url = `/pages/${code}/index`; if (code === "personal") { url = "/user_pages/myself/index"; @@ -48,7 +55,7 @@ const GuideBar = (props) => { Taro.redirectTo({ url: url, }).then(() => { - Taro.pageScrollTo({ + (Taro as any).pageScrollTo({ scrollTop: 0, duration: 300, }); diff --git a/src/components/UserInfo/index.tsx b/src/components/UserInfo/index.tsx index 2497df4..f96a563 100644 --- a/src/components/UserInfo/index.tsx +++ b/src/components/UserInfo/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } 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"; @@ -59,7 +59,7 @@ const on_edit = () => { }); }; // 用户信息卡片组件 -export const UserInfoCard: React.FC = ({ +const UserInfoCardComponent: React.FC = ({ editable = true, user_info, is_current_user, @@ -71,7 +71,20 @@ export const UserInfoCard: React.FC = ({ onTab, }) => { const { updateUserInfo } = useUserActions(); - console.log("UserInfoCard 用户信息:", user_info); + + // 使用 useRef 记录上一次的 user_info,只在真正变化时打印 + const prevUserInfoRef = useRef>(); + + useEffect(() => { + // 只在 user_info 真正变化时打印(通过 JSON 序列化比较) + const prevStr = JSON.stringify(prevUserInfoRef.current); + const currentStr = JSON.stringify(user_info); + if (prevStr !== currentStr) { + console.log("UserInfoCard 用户信息变化:", user_info); + prevUserInfoRef.current = user_info; + } + }, [user_info]); + // 编辑个人简介弹窗状态 const [edit_modal_visible, setEditModalVisible] = useState(false); const [editing_field, setEditingField] = useState(""); @@ -603,6 +616,23 @@ export const UserInfoCard: React.FC = ({ ); }; +// 自定义比较函数:只在关键 props 变化时重新渲染 +const arePropsEqual = (prevProps: UserInfoCardProps, nextProps: UserInfoCardProps) => { + // 使用 JSON.stringify 进行深度比较(注意:对于复杂对象可能有性能问题) + const prevUserInfoStr = JSON.stringify(prevProps.user_info); + const nextUserInfoStr = JSON.stringify(nextProps.user_info); + + return ( + prevUserInfoStr === nextUserInfoStr && + prevProps.editable === nextProps.editable && + prevProps.is_current_user === nextProps.is_current_user && + prevProps.is_following === nextProps.is_following + ); +}; + +// 使用 React.memo 优化组件,减少不必要的重新渲染 +export const UserInfoCard = React.memo(UserInfoCardComponent, arePropsEqual); + // 球局记录接口 export interface GameRecord { id: string; diff --git a/src/home_pages/index.tsx b/src/home_pages/index.tsx index 25a8fe8..e42b7f6 100644 --- a/src/home_pages/index.tsx +++ b/src/home_pages/index.tsx @@ -15,8 +15,8 @@ const HomePage: React.FC = () => { try { // 先获取用户信息 await fetchUserInfo(); - // 用户信息获取成功后跳转到列表页 - Taro.redirectTo({ url: '/game_pages/list/index' }); + // 用户信息获取成功后跳转到主容器页面 + Taro.redirectTo({ url: '/pages/main/index' }); } catch (error) { console.error('获取用户信息失败:', error); // 如果获取用户信息失败,跳转到登录页 diff --git a/src/pages/main/components/ListPageContent.tsx b/src/pages/main/components/ListPageContent.tsx new file mode 100644 index 0000000..34ce5ec --- /dev/null +++ b/src/pages/main/components/ListPageContent.tsx @@ -0,0 +1,396 @@ +import SearchBar from "@/components/SearchBar"; +import FilterPopup from "@/components/FilterPopup"; +import styles from "@/game_pages/list/index.module.scss"; +import { useEffect, useRef, useCallback, useState } from "react"; +import Taro from "@tarojs/taro"; +import { useListStore } from "@/store/listStore"; +import { useGlobalState } from "@/store/global"; +import { View, Image, Text, ScrollView } from "@tarojs/components"; +import ListContainer from "@/container/listContainer"; +import DistanceQuickFilter from "@/components/DistanceQuickFilter"; +import { updateUserLocation } from "@/services/userService"; +import { useUserActions } from "@/store/userStore"; +import { useDictionaryStore } from "@/store/dictionaryStore"; +import { saveImage } from "@/utils"; + +export interface ListPageContentProps { + onNavStateChange?: (state: { + isShowInputCustomerNavBar?: boolean; + isDistanceFilterVisible?: boolean; + isCityPickerVisible?: boolean; + }) => void; + onScrollToTop?: () => void; // 外部滚动到顶部方法(由主容器提供) + scrollToTopTrigger?: number; // 触发滚动的计数器 + onDistanceFilterVisibleChange?: (visible: boolean) => void; + onCityPickerVisibleChange?: (visible: boolean) => void; // 保留接口,但由主容器直接处理 +} + +const ListPageContent: React.FC = ({ + onNavStateChange, + onScrollToTop: _onScrollToTop, + scrollToTopTrigger, + onDistanceFilterVisibleChange, + onCityPickerVisibleChange: _onCityPickerVisibleChange, +}) => { + const store = useListStore() || {}; + const { fetchUserInfo } = useUserActions(); + const { statusNavbarHeightInfo, getCurrentLocationInfo } = useGlobalState() || {}; + const { totalHeight = 98 } = statusNavbarHeightInfo || {}; + + const { + listPageState, + loading, + error, + searchValue, + distanceData, + quickFilterData, + getMatchesData, + updateState, + updateListPageState, + updateFilterOptions, + clearFilterOptions, + initialFilterSearch, + loadMoreMatches, + fetchGetGamesCount, + updateDistanceQuickFilter, + getCities, + getCityQrCode, + area, + cityQrCode, + } = store; + + const { + isShowFilterPopup, + data: matches, + recommendList, + filterCount, + filterOptions, + distanceQuickFilter, + isShowInputCustomerNavBar, + pageOption, + isShowNoData, + } = listPageState || {}; + + const scrollContextRef = useRef(null); + const scrollViewRef = useRef(null); + const scrollTimeoutRef = useRef(null); + const lastScrollTopRef = useRef(0); + const scrollDirectionRef = useRef<'up' | 'down' | null>(null); + const lastScrollTimeRef = useRef(Date.now()); + const loadingMoreRef = useRef(false); + const scrollStartPositionRef = useRef(0); + const [showSearchBar, setShowSearchBar] = useState(true); + const [scrollTop, setScrollTop] = useState(0); + const [refreshing, setRefreshing] = useState(false); + + // 处理距离筛选显示/隐藏 + const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => { + onDistanceFilterVisibleChange?.(visible); + onNavStateChange?.({ isDistanceFilterVisible: visible }); + }, [onDistanceFilterVisibleChange, onNavStateChange]); + + // 处理城市选择器显示/隐藏(由主容器统一管理,通过 onNavStateChange 通知) + // 注意:CustomerNavBar 的 onCityPickerVisibleChange 由主容器直接处理 + + // 滚动到顶部(用于 ScrollView 内部滚动) + const scrollToTopInternal = useCallback(() => { + setScrollTop(prev => prev === 0 ? 0.1 : 0); + }, []); + + // 监听外部滚动触发 + useEffect(() => { + if (scrollToTopTrigger && scrollToTopTrigger > 0) { + scrollToTopInternal(); + } + }, [scrollToTopTrigger, scrollToTopInternal]); + + // ScrollView 滚动处理 + const handleScrollViewScroll = useCallback( + (e: any) => { + const currentScrollTop = e?.detail?.scrollTop || 0; + const lastScrollTop = lastScrollTopRef.current; + const currentTime = Date.now(); + const timeDiff = currentTime - lastScrollTimeRef.current; + + if (timeDiff < 100) return; + + const scrollDiff = currentScrollTop - lastScrollTop; + let newDirection = scrollDirectionRef.current; + if (Math.abs(scrollDiff) > 15) { + if (scrollDiff > 0) { + if (newDirection !== 'up') { + scrollStartPositionRef.current = lastScrollTop; + } + newDirection = 'up'; + } else { + if (newDirection !== 'down') { + scrollStartPositionRef.current = lastScrollTop; + } + newDirection = 'down'; + } + scrollDirectionRef.current = newDirection; + } + + const totalScrollDistance = Math.abs(currentScrollTop - scrollStartPositionRef.current); + const positionThreshold = 120; + const distanceThreshold = 80; + + if (newDirection === 'up' && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold) { + if (showSearchBar || !isShowInputCustomerNavBar) { + setShowSearchBar(false); + updateListPageState({ + isShowInputCustomerNavBar: true, + }); + onNavStateChange?.({ isShowInputCustomerNavBar: true }); + scrollStartPositionRef.current = currentScrollTop; + } + } else if ((newDirection === 'down' && totalScrollDistance > distanceThreshold) || currentScrollTop <= positionThreshold) { + if (!showSearchBar || isShowInputCustomerNavBar) { + setShowSearchBar(true); + updateListPageState({ + isShowInputCustomerNavBar: false, + }); + onNavStateChange?.({ isShowInputCustomerNavBar: false }); + scrollStartPositionRef.current = currentScrollTop; + } + } + + lastScrollTopRef.current = currentScrollTop; + lastScrollTimeRef.current = currentTime; + }, + [showSearchBar, isShowInputCustomerNavBar, updateListPageState, onNavStateChange] + ); + + useEffect(() => { + getLocation(); + fetchUserInfo(); + getCities(); + getCityQrCode(); + }, []); + + useEffect(() => { + if (pageOption?.page === 1 && matches?.length > 0) { + setShowSearchBar(true); + updateListPageState({ + isShowInputCustomerNavBar: false, + }); + onNavStateChange?.({ isShowInputCustomerNavBar: false }); + } + }, [matches, pageOption?.page, updateListPageState, onNavStateChange]); + + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + }; + }, []); + + const getLocation = async () => { + const location = await getCurrentLocationInfo(); + updateState({ location }); + if (location && location.latitude && location.longitude) { + try { + await updateUserLocation(location.latitude, location.longitude); + } catch (error) { + console.error("更新用户位置失败:", error); + } + } + fetchGetGamesCount(); + getMatchesData(); + return location; + }; + + const refreshMatches = async () => { + await initialFilterSearch(true); + }; + + const handleRefresh = async () => { + setRefreshing(true); + try { + await refreshMatches(); + } catch (error) { + (Taro as any).showToast({ + title: "刷新失败,请重试", + icon: "error", + duration: 1000, + }); + } finally { + setTimeout(() => { + setRefreshing(false); + }, 0); + } + }; + + const handleFilterConfirm = () => { + toggleShowPopup(); + getMatchesData(); + }; + + const toggleShowPopup = () => { + updateListPageState({ + isShowFilterPopup: !isShowFilterPopup, + }); + }; + + const handleUpdateFilterOptions = (params: Record) => { + updateFilterOptions(params); + }; + + const handleSearchChange = () => { }; + + const handleDistanceOrQuickChange = (name, value) => { + updateDistanceQuickFilter({ + [name]: value, + }); + }; + + const handleSearchClick = () => { + (Taro as any).navigateTo({ + url: "/game_pages/search/index", + }); + }; + + const initDictionaryData = async () => { + try { + const { fetchDictionary } = useDictionaryStore.getState(); + await fetchDictionary(); + } catch (error) { + console.error("初始化字典数据失败:", error); + } + }; + + useEffect(() => { + initDictionaryData(); + }, []); + + const area_city = area?.at(-2) || "上海"; + + function renderCityQrcode() { + let item = cityQrCode.find((item) => item.city_name === area_city); + if (!item) item = cityQrCode.find((item) => item.city_name === "其他"); + return ( + + {item ? ( + + + 当前城市暂无球局 + + 加入城市球友群,获得最新球局消息 + + + + { + saveImage(item.qr_code_url); + }} + /> + + 点击图片保存,使用微信扫码加入群聊 + + + + ) : ( + + 当前城市暂无球局, 敬请期待 + + )} + + ); + } + + return ( + <> + {area_city !== "上海" ? ( + renderCityQrcode() + ) : ( + + + {isShowFilterPopup && ( + + + + )} + + + 0} + filterCount={filterCount} + onChange={handleSearchChange} + value={searchValue} + onInputClick={handleSearchClick} + /> + + + + + + + { + if (!loading && !loadingMoreRef.current && listPageState?.isHasMoreData) { + loadingMoreRef.current = true; + try { + await loadMoreMatches(); + } catch (error) { + console.error("加载更多失败:", error); + } finally { + loadingMoreRef.current = false; + } + } + }} + onScroll={handleScrollViewScroll} + > + + + + + )} + + ); +}; + +export default ListPageContent; + diff --git a/src/pages/main/components/MessagePageContent.tsx b/src/pages/main/components/MessagePageContent.tsx new file mode 100644 index 0000000..c3d2829 --- /dev/null +++ b/src/pages/main/components/MessagePageContent.tsx @@ -0,0 +1,196 @@ +import { useState, useEffect } from "react"; +import { View, Text, Image, ScrollView } from "@tarojs/components"; +import { EmptyState } from "@/components"; +import noticeService from "@/services/noticeService"; +import { formatRelativeTime } from "@/utils/timeUtils"; +import Taro from "@tarojs/taro"; +import "@/other_pages/message/index.scss"; + +interface MessageItem { + id: string; + notification_type: string; + title: string; + content: string; + create_time: string; + is_read: number; + related_user_avatar?: string; + related_user_nickname?: string; + activity_image?: string; + jump_url?: string; +} + +type MessageCategory = "comment" | "follow"; + +const MessagePageContent = () => { + const [activeTab, setActiveTab] = useState(null); + const [messageList, setMessageList] = useState([]); + const [loading, setLoading] = useState(false); + const [reachedBottom, setReachedBottom] = useState(false); + const [refreshing, setRefreshing] = useState(false); + + const getNoticeList = async () => { + if (loading) return; + setLoading(true); + try { + const res = await noticeService.getNotificationList({}); + if (res.code === 0) { + setMessageList(res.data.list || []); + } + } catch (e) { + (Taro as any).showToast({ + title: "获取列表失败,请重试", + icon: "none", + duration: 2000, + }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getNoticeList(); + }, []); + + const filteredMessages = messageList; + + const handleTabClick = (tab: MessageCategory) => { + if (tab === "comment") { + (Taro as any).navigateTo({ + url: "/other_pages/comment_reply/index", + }); + return; + } + + if (tab === "follow") { + (Taro as any).navigateTo({ + url: "/other_pages/new_follow/index", + }); + return; + } + + setActiveTab(activeTab === tab ? null : tab); + }; + + const handleViewDetail = (message: MessageItem) => { + if (!message.jump_url) { + console.log("暂无跳转链接"); + return; + } + + (Taro as any).navigateTo({ + url: message.jump_url, + }).catch(() => { + (Taro as any).showToast({ + title: "页面不存在", + icon: "none", + duration: 2000, + }); + }); + }; + + const handleScrollToLower = () => { + if (!reachedBottom && filteredMessages.length > 0) { + setReachedBottom(true); + setTimeout(() => { + setReachedBottom(false); + }, 2000); + } + }; + + const handleRefresh = async () => { + setRefreshing(true); + try { + const res = await noticeService.getNotificationList({}); + if (res.code === 0) { + setMessageList(res.data.list || []); + } + } catch (e) { + (Taro as any).showToast({ + title: "刷新失败", + icon: "none", + duration: 2000, + }); + } finally { + setRefreshing(false); + } + }; + + return ( + + + handleTabClick("comment")} + > + + 评论和回复 + + handleTabClick("follow")} + > + + 新增关注 + + + + + {filteredMessages.length > 0 ? ( + + {filteredMessages.map((message) => ( + handleViewDetail(message)}> + + {message.title} + + + {formatRelativeTime(message.create_time)} + + + {message.content} + + + + + 查看详情 + + + + + + + ))} + {filteredMessages.length > 0 && ( + + 到底了 + + )} + + ) : ( + + )} + + + ); +}; + +export default MessagePageContent; + diff --git a/src/pages/main/components/MyselfPageContent.tsx b/src/pages/main/components/MyselfPageContent.tsx new file mode 100644 index 0000000..2880841 --- /dev/null +++ b/src/pages/main/components/MyselfPageContent.tsx @@ -0,0 +1,233 @@ +import React, { useState, useEffect } from "react"; +import { View, Text, Image, ScrollView } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import "@/user_pages/myself/index.scss"; +import { UserInfoCard } from "@/components/UserInfo/index"; +import { UserService } from "@/services/userService"; +import ListContainer from "@/container/listContainer"; +import { TennisMatch } from "@/../types/list/types"; +import { NTRPTestEntryCard } from "@/components"; +import { EvaluateScene } from "@/store/evaluateStore"; +import { useUserInfo } from "@/store/userStore"; +import { usePickerOption } from "@/store/pickerOptionsStore"; + +const MyselfPageContent: React.FC = () => { + const pickerOption = usePickerOption(); + const instance = (Taro as any).getCurrentInstance(); + const user_id = instance.router?.params?.userid || ""; + const is_current_user = !user_id; + const user_info = useUserInfo(); + + const [game_records, set_game_records] = useState([]); + const [ended_game_records, setEndedGameRecords] = useState([]); + const [loading] = useState(false); + const [is_following, setIsFollowing] = useState(false); + const [active_tab, setActiveTab] = useState<"hosted" | "participated">("hosted"); + + useEffect(() => { + pickerOption.getCities(); + pickerOption.getProfessions(); + }, []); + + const { useDidShow } = Taro as any; + useDidShow(() => { + // 确保从编辑页面返回时刷新数据 + }); + + useEffect(() => { + if (!loading) { + load_game_data(); + } + }, [active_tab]); + + const classifyGameRecords = ( + game_records: TennisMatch[] + ): { notEndGames: TennisMatch[]; finishedGames: TennisMatch[] } => { + const now = new Date().getTime(); + return game_records.reduce( + (result, cur) => { + let { end_time } = cur; + end_time = end_time.replace(/\s/, "T"); + new Date(end_time).getTime() > now + ? result.notEndGames.push(cur) + : result.finishedGames.push(cur); + return result; + }, + { + notEndGames: [] as TennisMatch[], + finishedGames: [] as TennisMatch[], + } + ); + }; + + const load_game_data = async () => { + try { + if (!user_info || !('id' in user_info)) { + return; + } + let games_data; + if (active_tab === "hosted") { + games_data = await UserService.get_hosted_games(user_info.id); + } else { + games_data = await UserService.get_participated_games(user_info.id); + } + const sorted_games = games_data.sort((a, b) => { + return ( + new Date(a.original_start_time.replace(/\s/, "T")).getTime() - + new Date(b.original_start_time.replace(/\s/, "T")).getTime() + ); + }); + const { notEndGames, finishedGames } = classifyGameRecords(sorted_games); + set_game_records(notEndGames); + setEndedGameRecords(finishedGames); + } catch (error) { + console.error("加载球局数据失败:", error); + } + }; + + const handle_follow = async () => { + try { + const new_following_state = await UserService.toggle_follow( + user_id, + is_following + ); + setIsFollowing(new_following_state); + + (Taro as any).showToast({ + title: new_following_state ? "关注成功" : "已取消关注", + icon: "success", + duration: 1500, + }); + } catch (error) { + console.error("关注操作失败:", error); + (Taro as any).showToast({ + title: "操作失败,请重试", + icon: "error", + duration: 2000, + }); + } + }; + + const goPublish = () => { + (Taro as any).navigateTo({ + url: "/publish_pages/publishBall/index", + }); + }; + + const handle_game_orders = () => { + (Taro as any).navigateTo({ + url: "/order_pages/orderList/index", + }); + }; + + const handle_wallet = () => { + (Taro as any).navigateTo({ + url: "/user_pages/wallet/index", + }); + }; + + const handleOnTab = (tab) => { + setActiveTab(tab); + }; + + return ( + + + + + + + + + 球局订单 + + + + + 钱包 + + + + + + + + + + + + setActiveTab("hosted")} + > + 我主办的 + + setActiveTab("participated")} + > + 我参与的 + + + + + + + {}} + collapse={true} + style={{ paddingBottom: 0, overflow: "hidden" }} + defaultShowNum={3} + /> + + + + 往期球局 + + + {}} + collapse={true} + style={{ paddingBottom: "90px", overflow: "hidden" }} + defaultShowNum={3} + /> + + + + + ); +}; + +export default MyselfPageContent; + diff --git a/src/pages/main/index.config.ts b/src/pages/main/index.config.ts new file mode 100644 index 0000000..2f5ac1d --- /dev/null +++ b/src/pages/main/index.config.ts @@ -0,0 +1,5 @@ +export default definePageConfig({ + navigationBarTitleText: '首页', + navigationStyle: 'custom', + navigationBarBackgroundColor: '#FAFAFA' + }) \ No newline at end of file diff --git a/src/pages/main/index.scss b/src/pages/main/index.scss new file mode 100644 index 0000000..8699fd2 --- /dev/null +++ b/src/pages/main/index.scss @@ -0,0 +1,52 @@ +.main-page { + width: 100%; + height: 100vh; + position: relative; + overflow: hidden; + background-color: #FAFAFA; + display: flex; + flex-direction: column; + + .tab-container { + width: 100%; + flex: 1; + position: relative; + overflow: hidden; + } + + .tab-content { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + opacity: 0; + transform: translateX(100%); + transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + pointer-events: none; + + &.active { + opacity: 1; + transform: translateX(0); + z-index: 1; + pointer-events: auto; + } + } + + // 隐藏所有子页面中的GuideBar(使用全局样式) + .tab-content .guide-bar-container { + display: none !important; + } +} + +// GuideBar z-index 控制 +.guide-bar-low-z-index { + z-index: 80 !important; +} + +.guide-bar-high-z-index { + z-index: 900 !important; +} + diff --git a/src/pages/main/index.tsx b/src/pages/main/index.tsx new file mode 100644 index 0000000..35f6c23 --- /dev/null +++ b/src/pages/main/index.tsx @@ -0,0 +1,191 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { View } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import { check_login_status } from "@/services/loginService"; +import { useUserActions } from "@/store/userStore"; +import GuideBar from "@/components/GuideBar"; +import { withAuth, BackNavbar, GeneralNavbar } from "@/components"; +import CustomerNavBar from "@/container/listCustomNavbar"; +import ListPageContent from "./components/ListPageContent"; +import MessagePageContent from "./components/MessagePageContent"; +import MyselfPageContent from "./components/MyselfPageContent"; +import "./index.scss"; + +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 [isShowInputCustomerNavBar, setIsShowInputCustomerNavBar] = useState(false); + const [listPageScrollToTopTrigger, setListPageScrollToTopTrigger] = useState(0); + + const { fetchUserInfo } = useUserActions(); + + // 初始化:检查登录状态并获取用户信息 + useEffect(() => { + const init = async () => { + const login_status = check_login_status(); + if (login_status) { + try { + await fetchUserInfo(); + } catch (error) { + console.error('获取用户信息失败:', error); + } + } + }; + init(); + }, [fetchUserInfo]); + + // 处理标签切换 + const handleTabChange = useCallback((code: string) => { + if (code === currentTab) { + return; + } + setCurrentTab(code as TabType); + // 切换标签时滚动到顶部 + (Taro as any).pageScrollTo({ + scrollTop: 0, + duration: 300, + }); + }, [currentTab]); + + // 处理发布菜单显示/隐藏 + const handlePublishMenuVisibleChange = useCallback((visible: boolean) => { + setIsPublishMenuVisible(visible); + }, []); + + // 处理距离筛选显示/隐藏 + const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => { + setIsDistanceFilterVisible(visible); + }, []); + + // 处理城市选择器显示/隐藏 + const handleCityPickerVisibleChange = useCallback((visible: boolean) => { + setIsCityPickerVisible(visible); + }, []); + + // 处理列表页导航状态变化 + const handleListNavStateChange = useCallback((state: { + isShowInputCustomerNavBar?: boolean; + isDistanceFilterVisible?: boolean; + isCityPickerVisible?: boolean; + }) => { + if (state.isShowInputCustomerNavBar !== undefined) { + setIsShowInputCustomerNavBar(state.isShowInputCustomerNavBar); + } + if (state.isDistanceFilterVisible !== undefined) { + setIsDistanceFilterVisible(state.isDistanceFilterVisible); + } + if (state.isCityPickerVisible !== undefined) { + setIsCityPickerVisible(state.isCityPickerVisible); + } + }, []); + + // 滚动到顶部 + const scrollToTop = useCallback(() => { + // 如果当前是列表页,触发列表页内部滚动 + if (currentTab === "list") { + // 通过状态变化触发 ListPageContent 内部滚动 + setListPageScrollToTopTrigger(prev => prev + 1); + } else { + // 其他页面使用 pageScrollTo + (Taro as any).pageScrollTo({ + scrollTop: 0, + duration: 300, + }); + } + }, [currentTab]); + + // 动态控制 GuideBar 的 z-index + useEffect(() => { + if (isPublishMenuVisible) { + setGuideBarZIndex('high'); + } else if (isDistanceFilterVisible || isCityPickerVisible) { + setGuideBarZIndex('low'); + } else { + setGuideBarZIndex('high'); + } + }, [isPublishMenuVisible, isDistanceFilterVisible, isCityPickerVisible]); + + // 渲染自定义导航栏(参考原始页面的实现) + const renderCustomNavbar = () => { + if (currentTab === "list") { + // 列表页:使用 CustomerNavBar(与原始列表页一致) + return ( + { + setIsCityPickerVisible(visible); + handleListNavStateChange({ isCityPickerVisible: visible }); + }} + onScrollToTop={scrollToTop} + /> + ); + } else if (currentTab === "message") { + // 消息页:使用 BackNavbar(与原始消息页一致,只传递 title) + return ( + + ); + } else if (currentTab === "personal") { + // 我的页:使用 GeneralNavbar(参考原始实现) + return ( + + ); + } + return null; + }; + + return ( + + {/* 自定义导航栏 */} + {renderCustomNavbar()} + + {/* 列表页内容 */} + + + + + {/* 消息页内容 */} + + + + + {/* 我的页内容 */} + + + + + {/* 底部导航栏 */} + + + ); +}; + +export default withAuth(MainPage); +