333 lines
10 KiB
TypeScript
333 lines
10 KiB
TypeScript
import React, { useState, useEffect, useCallback } from "react";
|
||
import { View } from "@tarojs/components";
|
||
import Taro, { useRouter, useShareAppMessage } from "@tarojs/taro";
|
||
import { OSS_BASE } from "@/config/api";
|
||
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";
|
||
import HomeNavbar from "@/components/HomeNavbar";
|
||
import ListPageContent from "./components/ListPageContent";
|
||
import MessagePageContent from "./components/MessagePageContent";
|
||
import MyselfPageContent from "./components/MyselfPageContent";
|
||
import "./index.scss";
|
||
import FamilyContext from "@/context";
|
||
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||
|
||
type TabType = "list" | "message" | "personal";
|
||
|
||
const MainPage: React.FC = () => {
|
||
const { params } = useRouter();
|
||
const [currentTab, setCurrentTab] = useState<TabType>(() => {
|
||
const tab = params?.tab as TabType | undefined;
|
||
return tab === "list" || tab === "message" || tab === "personal" ? tab : "list";
|
||
});
|
||
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
|
||
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 [showAuthError, setShowAuthError] = useState(false);
|
||
const [authErrorMessage, setAuthErrorMessage] = useState("");
|
||
|
||
const { fetchUserInfo, checkNicknameChangeStatus } = useUserActions();
|
||
// 从 store 获取 GuideBar 相关状态和方法
|
||
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
|
||
useGlobalState();
|
||
|
||
// 从分享链接进入时根据 ?tab= 定位到对应 tab
|
||
useEffect(() => {
|
||
const tab = params?.tab as TabType | undefined;
|
||
if (tab === "list" || tab === "message" || tab === "personal") {
|
||
setCurrentTab(tab);
|
||
}
|
||
}, [params?.tab]);
|
||
|
||
// 初始化:自动微信授权并获取用户信息
|
||
useEffect(() => {
|
||
const init = async () => {
|
||
const hasValidToken = tokenManager.hasValidToken();
|
||
|
||
if (!hasValidToken) {
|
||
try {
|
||
console.log("开始微信授权...");
|
||
const loginRes = await wechat_auth_login();
|
||
|
||
if (loginRes.success && loginRes.token) {
|
||
save_login_state(loginRes.token, loginRes.user_info);
|
||
console.log("微信授权成功");
|
||
} else {
|
||
// 显示错误提示
|
||
setAuthErrorMessage(loginRes.message || "微信授权失败");
|
||
setShowAuthError(true);
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
console.warn("微信授权异常:", error);
|
||
setAuthErrorMessage("微信授权失败,请重试");
|
||
setShowAuthError(true);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果有有效token,获取用户详细信息
|
||
if (tokenManager.hasValidToken()) {
|
||
try {
|
||
await fetchUserInfo();
|
||
await checkNicknameChangeStatus();
|
||
} catch (error) {
|
||
console.warn("获取用户信息失败:", error);
|
||
}
|
||
}
|
||
};
|
||
init();
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, []); // fetchUserInfo 是稳定的函数引用,不需要加入依赖项
|
||
|
||
// 显示授权错误提示
|
||
useEffect(() => {
|
||
if (showAuthError) {
|
||
Taro.showModal({
|
||
title: "授权失败",
|
||
content: `${authErrorMessage}\n\n请重启小程序后重试`,
|
||
showCancel: false,
|
||
confirmText: "我知道了",
|
||
success: () => {
|
||
setShowAuthError(false);
|
||
},
|
||
});
|
||
}
|
||
}, [showAuthError, authErrorMessage]);
|
||
|
||
// 处理标签切换
|
||
const handleTabChange = useCallback(
|
||
(code: string) => {
|
||
if (code === currentTab) {
|
||
return;
|
||
}
|
||
|
||
// 切换标签时关闭城市选择器
|
||
if (isCityPickerVisible) {
|
||
setIsCityPickerVisible(false);
|
||
}
|
||
|
||
setCurrentTab(code as TabType);
|
||
// 切换标签时滚动到顶部
|
||
(Taro as any).pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 300,
|
||
});
|
||
},
|
||
[currentTab, isCityPickerVisible]
|
||
);
|
||
|
||
// 处理发布菜单显示/隐藏
|
||
const handlePublishMenuVisibleChange = useCallback((visible: boolean) => {
|
||
setIsPublishMenuVisible(visible);
|
||
}, []);
|
||
|
||
// 处理距离筛选显示/隐藏
|
||
const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => {
|
||
setIsDistanceFilterVisible(visible);
|
||
}, []);
|
||
|
||
// 处理城市选择器显示/隐藏
|
||
const handleCityPickerVisibleChange = useCallback((visible: boolean) => {
|
||
setIsCityPickerVisible(visible);
|
||
}, []);
|
||
|
||
// 处理筛选弹窗显示/隐藏
|
||
const handleFilterPopupVisibleChange = useCallback((visible: boolean) => {
|
||
setIsFilterPopupVisible(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);
|
||
}
|
||
},
|
||
[]
|
||
);
|
||
|
||
// 分享:按 tab 用 map 对应文案与分享图
|
||
const share_config: Record<
|
||
TabType,
|
||
{ title: string; image_path: string; query: string }
|
||
> = {
|
||
list: {
|
||
title: "有你就有场,发现身边好球友和好球局",
|
||
image_path: "system/share_home.png",
|
||
query: "?tab=list",
|
||
},
|
||
personal: {
|
||
title: "快来有场,约我一起打网球~",
|
||
image_path: "system/share_self.png",
|
||
query: "?tab=personal",
|
||
},
|
||
message: {
|
||
title: "查看球友动态",
|
||
image_path: "system/share_home.png",
|
||
query: "?tab=message",
|
||
},
|
||
};
|
||
useShareAppMessage(() => {
|
||
const config = share_config[currentTab] ?? {
|
||
title: "约球",
|
||
image_path: "system/share_home.png",
|
||
query: "",
|
||
};
|
||
// const imageUrl = OSS_BASE
|
||
// ? `${OSS_BASE.replace(/\/$/, "")}/${config.image_path}`
|
||
// : "";
|
||
return {
|
||
title: config.title,
|
||
path: "/main_pages/index" + config.query,
|
||
// imageUrl,
|
||
};
|
||
});
|
||
|
||
// 滚动到顶部
|
||
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 ||
|
||
isFilterPopupVisible
|
||
) {
|
||
setGuideBarZIndex("low");
|
||
} else {
|
||
setGuideBarZIndex("high");
|
||
}
|
||
}, [
|
||
isPublishMenuVisible,
|
||
isDistanceFilterVisible,
|
||
isCityPickerVisible,
|
||
isFilterPopupVisible,
|
||
]);
|
||
|
||
// 渲染自定义导航栏(参考原始页面的实现)
|
||
const renderCustomNavbar = () => {
|
||
if (currentTab === "list") {
|
||
// 列表页:使用 HomeNavbar(与原始列表页一致)
|
||
return (
|
||
<HomeNavbar
|
||
config={{
|
||
showInput: isShowInputCustomerNavBar,
|
||
}}
|
||
onCityPickerVisibleChange={(visible) => {
|
||
setIsCityPickerVisible(visible);
|
||
handleListNavStateChange({ isCityPickerVisible: visible });
|
||
}}
|
||
onScrollToTop={scrollToTop}
|
||
/>
|
||
);
|
||
} else if (currentTab === "message") {
|
||
// 消息页:使用 GeneralNavbar(与原始消息页一致,显示用户头像和标题)
|
||
return (
|
||
<GeneralNavbar
|
||
title="消息"
|
||
titlePosition="left"
|
||
showBack={false}
|
||
showAvatar={true}
|
||
/>
|
||
);
|
||
} else if (currentTab === "personal") {
|
||
// 我的页:使用 GeneralNavbar 显示标题
|
||
return <GeneralNavbar title="" titlePosition="left" showBack={false} />;
|
||
}
|
||
return null;
|
||
};
|
||
|
||
const handleGrandchildTrigger = (value) => {
|
||
console.log("[MainPage] handleGrandchildTrigger called with:", value);
|
||
setShowGuideBar(!value);
|
||
};
|
||
|
||
return (
|
||
<FamilyContext.Provider value={{ handleGrandchildTrigger }}>
|
||
<View className="main-page">
|
||
{/* 自定义导航栏 */}
|
||
{renderCustomNavbar()}
|
||
|
||
{/* 列表页内容 */}
|
||
<View
|
||
className={`tab-content ${currentTab === "list" ? "active" : ""}`}
|
||
>
|
||
<ListPageContent
|
||
isActive={currentTab === "list"}
|
||
onNavStateChange={handleListNavStateChange}
|
||
onScrollToTop={scrollToTop}
|
||
scrollToTopTrigger={listPageScrollToTopTrigger}
|
||
onDistanceFilterVisibleChange={handleDistanceFilterVisibleChange}
|
||
onCityPickerVisibleChange={handleCityPickerVisibleChange}
|
||
onFilterPopupVisibleChange={handleFilterPopupVisibleChange}
|
||
/>
|
||
</View>
|
||
|
||
{/* 消息页内容 */}
|
||
<View
|
||
className={`tab-content ${currentTab === "message" ? "active" : ""}`}
|
||
>
|
||
<MessagePageContent isActive={currentTab === "message"} />
|
||
</View>
|
||
|
||
{/* 我的页内容 */}
|
||
<View
|
||
className={`tab-content ${currentTab === "personal" ? "active" : ""}`}
|
||
>
|
||
<MyselfPageContent isActive={currentTab === "personal"} />
|
||
</View>
|
||
|
||
{/* 底部导航栏 */}
|
||
{showGuideBar ? (
|
||
<GuideBar
|
||
currentPage={currentTab}
|
||
guideBarClassName={
|
||
guideBarZIndex === "low"
|
||
? "guide-bar-low-z-index"
|
||
: "guide-bar-high-z-index"
|
||
}
|
||
onTabChange={handleTabChange}
|
||
onPublishMenuVisibleChange={handlePublishMenuVisibleChange}
|
||
/>
|
||
) : null}
|
||
</View>
|
||
</FamilyContext.Provider>
|
||
);
|
||
};
|
||
|
||
export default MainPage;
|