diff --git a/README.md b/README.md index b98f64d..f6aa878 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ npm run build:alipay # 支付宝小程序 ## 页面路由 -- `/game_pages/list/index` - 首页 + - `/pages/publish/publish` - 原发布页面 - `/pages/publishBall/publishBall` - 新的约球发布页面 ⭐ - `/pages/dynamicFormDemo/dynamicFormDemo` - 动态表单演示页面 ⭐ diff --git a/config/prod.ts b/config/prod.ts index 7fd7f3a..3eb4278 100644 --- a/config/prod.ts +++ b/config/prod.ts @@ -31,22 +31,6 @@ export default { // * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。 // * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer // */ - // chain.plugin('analyzer') - // .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) - // /** - // * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。 - // * @docs https://github.com/chrisvfritz/prerender-spa-plugin - // */ - // const path = require('path') - // const Prerender = require('prerender-spa-plugin') - // const staticDir = path.join(__dirname, '..', 'dist') - // chain - // .plugin('prerender') - // .use(new Prerender({ - // staticDir, - // routes: [ '/game_pages/list/index' ], - // postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') }) - // })) - // } + } } satisfies UserConfigExport<'webpack5'> diff --git a/docs/z-index-guide.md b/docs/z-index-guide.md index efb6e1e..c56370e 100644 --- a/docs/z-index-guide.md +++ b/docs/z-index-guide.md @@ -45,8 +45,7 @@ ### GuideBar(底部导航栏)动态控制 -**实现位置**: -- `src/game_pages/list/index.tsx`(主逻辑) + - `src/components/DistanceQuickFilter/index.tsx`(筛选菜单回调) - `src/components/PublishMenu/PublishMenu.tsx`(发布菜单回调) - `src/container/listCustomNavbar/index.tsx`(城市选择器回调) diff --git a/fetchUserInfo_analysis.md b/fetchUserInfo_analysis.md index bc10391..c4097e3 100644 --- a/fetchUserInfo_analysis.md +++ b/fetchUserInfo_analysis.md @@ -12,9 +12,6 @@ - 微信授权成功后调用 - ✅ 合理(主入口,需要确保用户信息加载) -3. **src/game_pages/list/index.tsx** (第228行) - - 在 useEffect 中,等待 waitForAuthInit 后调用 - - ✅ 合理 4. **src/game_pages/detail/index.tsx** (第55行) - 在 useEffect 中,等待 waitForAuthInit 后调用 diff --git a/src/app.config.ts b/src/app.config.ts index 369173a..74e3ad2 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -54,7 +54,6 @@ export default defineAppConfig({ { root: 'other_pages', pages: [ - "message/index", "comment_reply/index", // 收到的评论和回复 "new_follow/index", // 新增关注 "favorites/index", // 收藏页 diff --git a/src/components/GuideBar/index.tsx b/src/components/GuideBar/index.tsx index 7e4b240..c45a194 100644 --- a/src/components/GuideBar/index.tsx +++ b/src/components/GuideBar/index.tsx @@ -47,25 +47,7 @@ const GuideBar = (props) => { return; } - // 否则使用路由跳转(兼容模式) - let url = `/pages/${code}/index`; - if (code === "personal") { - url = "/user_pages/myself/index"; - } - if (code === "message") { - url = "/other_pages/message/index"; - } - if (code === "list") { - url = "/main_pages/index" - } - redirectTo({ - url: url, - }).then(() => { - (Taro as any).pageScrollTo({ - scrollTop: 0, - duration: 300, - }); - }); + }; return ( diff --git a/src/components/NTRPTestEntryCard/index.tsx b/src/components/NTRPTestEntryCard/index.tsx index 484e00e..9730c32 100644 --- a/src/components/NTRPTestEntryCard/index.tsx +++ b/src/components/NTRPTestEntryCard/index.tsx @@ -58,10 +58,10 @@ function NTRPTestEntryCard(props: { setCallback({ type, next: () => { - Taro.redirectTo({ url: "/game_pages/list/index" }); + Taro.redirectTo({ url: "/main_pages/index" }); }, onCancel: () => { - // Taro.redirectTo({ url: "/game_pages/list/index" }); + Taro.navigateBack(); }, }); @@ -97,7 +97,7 @@ function NTRPTestEntryCard(props: { setCallback({ type, next: () => { - Taro.redirectTo({ url: "/game_pages/list/index" }); + Taro.redirectTo({ url: "/main_pages/index" }); }, onCancel: () => { // Taro.redirectTo({ url: "/user_pages/edit/index" }); diff --git a/src/game_pages/list/index.config.ts b/src/game_pages/list/index.config.ts deleted file mode 100644 index 36ec1ae..0000000 --- a/src/game_pages/list/index.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default definePageConfig({ - navigationBarTitleText: '', - enablePullDownRefresh: false, // 禁用页面级下拉刷新,使用 ScrollView 的下拉刷新 - backgroundTextStyle: 'dark', - navigationStyle: 'custom', - backgroundColor: '#FAFAFA', - onReachBottomDistance: 300, -}) diff --git a/src/game_pages/list/index.module.scss b/src/game_pages/list/index.module.scss deleted file mode 100644 index 47da177..0000000 --- a/src/game_pages/list/index.module.scss +++ /dev/null @@ -1,193 +0,0 @@ -// GuideBar 的 z-index 通过局部样式类动态控制 - -.cqContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; - - .wrapper { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - // gap: 24px; - - .tips { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - - .tip1 { - color: #000; - text-align: center; - font-family: "PingFang SC"; - font-size: 18px; - font-style: normal; - font-weight: 600; - line-height: 28px; - } - - .tip2 { - color: rgba(0, 0, 0, 0.65); - text-align: center; - font-family: "PingFang SC"; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: normal; - } - } - - .qrcodeWrappper { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 12px; - - .qrcode { - width: 180px; - height: 180px; - // border-radius: 12px; - // border: 1px solid rgba(0, 0, 0, 0.06); - // background: lightgray 50% / cover no-repeat; - // box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.16); - } - - .qrcodeTip { - color: rgba(0, 0, 0, 0.65); - text-align: center; - font-family: "PingFang SC"; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: normal; - margin-top: -30px; - } - } - } -} - -.listPage { - background-color: #FAFAFA; - font-family: "PingFang SC"; - transition: padding-top 0.3s ease-in-out; // 添加过渡动画,让布局变化更平滑 - display: flex; - flex-direction: column; - height: calc(100vh - var(--status-bar-height, 0px) - var(--nav-bar-height, 0px) - 112px); // 减去底部导航栏高度 112px - overflow: hidden; - - .fixedHeader { - position: sticky; - top: 0; - z-index: 90; - display: flex; - flex-direction: column; - } - - .listTopSearchWrapper { - // background-color: #fafafa; - // 使用 margin-top 负值来控制可见性,保持元素高度不变,筛选项位置固定 - transition: margin-top 0.25s cubic-bezier(0.4, 0, 0.2, 1), - opacity 0.2s ease-out; - padding: 10px 15px 10px 15px; // 统一边距:上下10px 左右15px - box-sizing: border-box; - overflow: hidden; - will-change: margin-top, opacity; - - &.show { - opacity: 1; - margin-top: 0; // 正常显示 - } - - &.hide { - opacity: 0; - margin-top: -64px; // 使用负 margin 向上隐藏,但仍占据布局空间 (44px内容 + 20px padding = 64px) - pointer-events: none; // 隐藏时禁用交互 - } - } - - .listTopFilterWrapper { - display: flex; - box-sizing: border-box; - align-items: center; - padding: 0 15px 10px 15px; - // 上0 左右15px 下10px(与搜索框左右对齐,下边距一致) - gap: 5px; - background-color: #fafafa; - } - - .listScrollView { - flex: 1; - height: 0; // 让 flex 生效 - } - - .menuFilter { - padding: 0; - } -} - -.listNavWrapper { - position: relative; -} - -.toggleElement { - /* 绝对定位使两个元素重叠 */ - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - - /* 过渡动画设置,实现平滑切换 */ - transition: opacity 0.5s ease, transform 0.5s ease; - - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - border-radius: 8px; -} - -/* 第一个元素样式 */ -.firstElement { - background-color: #4a90e2; - color: white; -} - -/* 第二个元素样式 */ -.secondElement { - background-color: #5cb85c; - color: white; - /* 初始状态:透明且稍微偏移 */ - opacity: 0; - transform: translateY(20px); -} - -/* 可见状态 */ -.visible { - opacity: 1; - transform: translateY(0); -} - -/* 隐藏状态 */ -.hidden { - opacity: 0; - transform: translateY(20px); - pointer-events: none; - /* 隐藏时不响应鼠标事件 */ -} - -// 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 deleted file mode 100644 index d47dfe0..0000000 --- a/src/game_pages/list/index.tsx +++ /dev/null @@ -1,613 +0,0 @@ -import SearchBar from "@/components/SearchBar"; -import FilterPopup from "@/components/FilterPopup"; -import styles from "./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 HomeNavbar from "@/components/HomeNavbar"; -import GuideBar from "@/components/GuideBar"; -import ListContainer from "@/container/listContainer"; -import DistanceQuickFilter from "@/components/DistanceQuickFilter"; -import { withAuth } from "@/components"; -import { updateUserLocation } from "@/services/userService"; -// import ShareCardCanvas from "@/components/ShareCardCanvas"; -import { useUserActions } from "@/store/userStore"; -import { useDictionaryStore } from "@/store/dictionaryStore"; -import { saveImage } from "@/utils"; -import { waitForAuthInit } from "@/utils/authInit"; - -const ListPage = () => { - // 从 store 获取数据和方法 - const store = useListStore() || {}; - - const { fetchUserInfo } = useUserActions(); - - const { statusNavbarHeightInfo, getCurrentLocationInfo } = - useGlobalState() || {}; - - const { totalHeight = 98 } = statusNavbarHeightInfo || {}; // 设置默认值,避免从0跳到实际值 - 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); // ScrollView 的 ref - 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); // 控制 ScrollView 滚动位置 - - // 动态控制 GuideBar 的 z-index - const [guideBarZIndex, setGuideBarZIndex] = useState<"low" | "high">("high"); - const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false); - const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false); - const [isCityPickerVisible, setIsCityPickerVisible] = useState(false); - - // 处理 PublishMenu 显示/隐藏 - const handlePublishMenuVisibleChange = useCallback((visible: boolean) => { - setIsPublishMenuVisible(visible); - }, []); - - // 处理 DistanceQuickFilter 显示/隐藏 - const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => { - setIsDistanceFilterVisible(visible); - }, []); - - // 处理 CityPicker 显示/隐藏 - const handleCityPickerVisibleChange = useCallback((visible: boolean) => { - setIsCityPickerVisible(visible); - }, []); - - // 滚动到顶部的方法 - const scrollToTop = useCallback(() => { - // 使用一个唯一值触发 scrollTop 更新,确保每次都能滚动到顶部 - setScrollTop((prev) => (prev === 0 ? 0.1 : 0)); - }, []); - - // 监听所有弹窗和菜单的状态,动态调整 GuideBar 的 z-index - useEffect(() => { - if (isPublishMenuVisible) { - // PublishMenu 展开时,GuideBar 保持高层级 - setGuideBarZIndex("high"); - } else if ( - isShowFilterPopup || - isDistanceFilterVisible || - isCityPickerVisible - ) { - // 任何筛选组件或选择器展开时,GuideBar 降低层级 - setGuideBarZIndex("low"); - } else { - // 都关闭时,GuideBar 保持高层级 - setGuideBarZIndex("high"); - } - }, [ - isShowFilterPopup, - isPublishMenuVisible, - isDistanceFilterVisible, - isCityPickerVisible, - ]); - - // 使用 ref 保存最新的状态值,避免依赖项变化导致函数重新创建 - const showSearchBarRef = useRef(showSearchBar); - const isShowInputCustomerNavBarRef = useRef(isShowInputCustomerNavBar); - - useEffect(() => { - showSearchBarRef.current = showSearchBar; - isShowInputCustomerNavBarRef.current = isShowInputCustomerNavBar; - }, [showSearchBar, isShowInputCustomerNavBar]); - - // 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; - - // 节流:提高到100ms,减少触发频率 - if (timeDiff < 100) return; - - // 计算滚动距离 - const scrollDiff = currentScrollTop - lastScrollTop; - - // 判断滚动方向(提高阈值到15px) - 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; // 需要滚动到距离顶部120px - const distanceThreshold = 80; // 需要连续滚动80px才触发 - - // 使用 ref 获取最新值,避免依赖项变化 - const currentShowSearchBar = showSearchBarRef.current; - const currentIsShowInputCustomerNavBar = isShowInputCustomerNavBarRef.current; - - if ( - newDirection === "up" && - currentScrollTop > positionThreshold && - totalScrollDistance > distanceThreshold - ) { - // 上滑超过阈值,且连续滚动距离足够,隐藏搜索框 - if (currentShowSearchBar || !currentIsShowInputCustomerNavBar) { - setShowSearchBar(false); - updateListPageState({ - isShowInputCustomerNavBar: true, - }); - // 重置起始位置 - scrollStartPositionRef.current = currentScrollTop; - } - } else if ( - (newDirection === "down" && totalScrollDistance > distanceThreshold) || - currentScrollTop <= positionThreshold - ) { - // 下滑且连续滚动距离足够,或者回到顶部附近,显示搜索框 - if (!currentShowSearchBar || currentIsShowInputCustomerNavBar) { - setShowSearchBar(true); - updateListPageState({ - isShowInputCustomerNavBar: false, - }); - // 重置起始位置 - scrollStartPositionRef.current = currentScrollTop; - } - } - - lastScrollTopRef.current = currentScrollTop; - lastScrollTimeRef.current = currentTime; - }, - [updateListPageState] - // 移除 showSearchBar 和 isShowInputCustomerNavBar 依赖,使用 ref 获取最新值 - ); - - useEffect(() => { - // 分批异步执行初始化操作,避免阻塞首屏渲染 - // 1. 立即执行:获取城市和二维码(轻量操作) - getCities(); - getCityQrCode(); - - // 2. 延迟执行:等待静默登录完成后获取用户信息 - requestAnimationFrame(async () => { - try { - await waitForAuthInit(); - await fetchUserInfo(); - } catch (error) { - console.error('获取用户信息失败:', error); - } - }); - - // 3. 延迟执行:获取位置信息(可能较慢,不阻塞首屏) - requestAnimationFrame(() => { - requestAnimationFrame(() => { - getLocation().catch((error) => { - console.error('获取位置信息失败:', error); - }); - }); - }); - }, []); - - // 监听数据变化,如果是第一页就恢复显示搜索框 - useEffect(() => { - if (pageOption?.page === 1 && matches?.length > 0) { - // 恢复搜索框显示 - setShowSearchBar(true); - updateListPageState({ - isShowInputCustomerNavBar: false, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [matches?.length, pageOption?.page]); - // 注意:updateListPageState 是稳定的函数引用,不需要加入依赖项 - // 只依赖实际会变化的数据:matches 的长度和 pageOption.page - - // 清理定时器 - useEffect(() => { - return () => { - if (scrollTimeoutRef.current) { - clearTimeout(scrollTimeoutRef.current); - } - }; - }, []); - - // 监听距离和排序方式变化,自动调用接口 - // useEffect(() => { - // // 只有当 distanceQuickFilter 有值时才调用接口 - // if (distanceQuickFilter?.distanceFilter !== undefined || distanceQuickFilter?.order !== undefined) { - - // // if (distanceQuickFilter?.quick !== "0") { - // getMatchesData(); - // // } - // } - // }, [distanceQuickFilter?.distanceFilter, distanceQuickFilter?.order]); - - // 获取位置信息 - const getLocation = async () => { - const location = await getCurrentLocationInfo(); - - // 保存位置到全局状态 - updateState({ location }); - - // 同时更新用户位置到后端和 store - 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 [refreshing, setRefreshing] = useState(false); - - // ScrollView 下拉刷新处理函数 - const handleRefresh = async () => { - setRefreshing(true); - try { - // 调用刷新方法 - await refreshMatches(); - } catch (error) { - Taro.showToast({ - title: "刷新失败,请重试", - icon: "error", - duration: 1000, - }); - } finally { - // 使用 requestAnimationFrame 替代 setTimeout(0),性能更好 - requestAnimationFrame(() => { - setRefreshing(false); - }); - } - }; - - /** - * @description 综合筛选确认 - * @returns - */ - const handleFilterConfirm = () => { - toggleShowPopup(); - getMatchesData(); - }; - - /** - * @description 综合筛选弹框 - * @returns - */ - const toggleShowPopup = () => { - updateListPageState({ - isShowFilterPopup: !isShowFilterPopup, - }); - }; - - /** - * @description 更新筛选条件 - * @param {Record} params 筛选项 - */ - const handleUpdateFilterOptions = (params: Record) => { - updateFilterOptions(params); - }; - - const handleSearchChange = () => {}; - - // 距离筛选 - const handleDistanceOrQuickChange = (name, value) => { - updateDistanceQuickFilter({ - [name]: value, - }); - // updateListPageState({ - // distanceQuickFilter: { - // ...distanceQuickFilter, - // [name]: value, - // }, - // }); - }; - - const handleSearchClick = () => { - Taro.navigateTo({ - url: "/game_pages/search/index", - }); - }; - - // ====== 分享 测试 ====== - // const [shareImagePath, setShareImagePath] = useState('') - - // const handleShare = (imagePath: string) => { - // console.log('===imagePath', imagePath) - - // // 避免重复设置相同的图片路径 - // if (imagePath && imagePath !== shareImagePath) { - // setShareImagePath(imagePath) - - // // 图片生成完成后,显示分享菜单 - // Taro.showShareMenu({ - // withShareTicket: true, - // success: () => { - // console.log('分享菜单显示成功') - // }, - // fail: (error) => { - // console.error('分享菜单显示失败:', error) - // } - // }) - // } - // } - - // // 页面级分享钩子 - // useShareAppMessage(() => { - // console.log('页面分享给好友,图片路径:', shareImagePath) - // return { - // title: '列表页-邀你加入球局', - // path: '/game_pages/list/index', - // imageUrl: shareImagePath || '' - // } - // }) - - // useShareTimeline(() => { - // console.log('页面分享到朋友圈,图片路径:', shareImagePath) - // return { - // title: '列表页-邀你加入球局', - // query: 'from=timeline', - // imageUrl: shareImagePath || '' - // } - // }) - // 初始化字典数据 - const initDictionaryData = async () => { - try { - const { fetchDictionary } = useDictionaryStore.getState(); - await fetchDictionary(); - } catch (error) { - console.error("初始化字典数据失败:", error); - } - }; - - useEffect(() => { - initDictionaryData(); - }, []); - - // useEffect(() => { - // generateShareImage({ - // userAvatar: "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f62c80-ac44-4f3b-bb6c-d7f6e8ebf76d.jpg", - // userNickname: "华巴抡卡", - // gameType: "单打", - // skillLevel: "NTRP 2.5 - 3.0", - // gameDate: "6月20日(周五)", - // gameTime: "下午5点 2小时", - // venueName: "因乐驰网球俱乐部(嘉定江桥万达店)", - // venueImages: ["https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f62c80-ac44-4f3b-bb6c-d7f6e8ebf76d.jpg", - // //"https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f62c80-ac44-4f3b-bb6c-d7f6e8ebf76d.jpg" - // ], - // playerImage: "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f62c80-ac44-4f3b-bb6c-d7f6e8ebf76d.jpg" - // }).then((imagePath) => { - // console.log('===imagePath666', imagePath) - // if (imagePath) { - // setShareImagePath(imagePath) - // } - // }) - // }, []) - - 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} - /> - - {/* 筛选 - 始终显示,固定在原位置 */} - - - - - - {/* 可滚动的列表内容 */} - { - // 防止重复调用,检查 loading 状态和是否正在加载更多 - 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 ListPage; diff --git a/src/other_pages/message/index.scss b/src/main_pages/components/MessagePageContent.module.scss similarity index 90% rename from src/other_pages/message/index.scss rename to src/main_pages/components/MessagePageContent.module.scss index f03a5a2..1fe26ff 100644 --- a/src/other_pages/message/index.scss +++ b/src/main_pages/components/MessagePageContent.module.scss @@ -1,6 +1,6 @@ @use "~@/scss/images.scss" as img; -.message-container { +.messageContainer { width: 100%; height: 100vh; box-sizing: border-box; @@ -10,7 +10,7 @@ // 分类标签区 - .category-tabs { + .categoryTabs { display: flex; align-items: stretch; justify-content: space-between; @@ -19,19 +19,19 @@ box-sizing: border-box; height: 124px; - .tab-item { + .tabItem { display: flex; flex-direction: column; justify-content: center; align-items: center; width: 161px; - .tab-icon-wrapper { + .tabIconWrapper { position: relative; width: 56px; height: 56px; - .tab-icon { + .tabIcon { width: 56px; height: 56px; border-radius: 56px; @@ -61,14 +61,14 @@ } } - .tab-icon { + .tabIcon { width: 56px; height: 56px; border-radius: 56px; transition: all 0.3s; } - .tab-text { + .tabText { font-family: "PingFang SC"; font-weight: 600; font-size: 16px; @@ -77,14 +77,14 @@ } &.active { - .tab-icon { + .tabIcon { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); transform: scale(1.05); } } &:active { - .tab-icon { + .tabIcon { transform: scale(0.95); } } @@ -92,14 +92,14 @@ } // 消息列表 - // .message-list { + // .messageList { // flex: 1; // overflow: hidden; // box-sizing: border-box; // // margin-bottom:100px; // background-color: none !important; - // .message-list-content { + // .messageListContent { // display: flex; // flex-direction: column; // padding: 12px 12px 112px; @@ -113,10 +113,10 @@ // } // 消息滚动区域 - .message-scroll { + .messageScroll { flex: 1; overflow: hidden; - .message-cards { + .messageCards { padding: 12px; display: flex; flex-direction: column; @@ -125,7 +125,7 @@ } // 系统消息卡片 - .message-card { + .messageCard { background: #ffffff; border: 0.5px solid rgba(0, 0, 0, 0.08); border-radius: 20px; @@ -139,10 +139,10 @@ opacity: 0.95; } - .card-title-row { + .cardTitleRow { padding: 12px 15px 0; - .card-title { + .cardTitle { font-family: "PingFang SC"; font-weight: 600; font-size: 16px; @@ -151,13 +151,13 @@ } } - .card-time-row { + .cardTimeRow { padding: 4px 15px 0; display: flex; align-items: center; gap: 2px; - .card-time { + .cardTime { font-family: "PingFang SC"; font-weight: 400; font-size: 12px; @@ -166,13 +166,13 @@ } } - .card-content-row { + .cardContentRow { padding: 8px 15px 0; display: flex; align-items: stretch; gap: 2px; - .card-content { + .cardContent { font-family: "PingFang SC"; font-weight: 400; font-size: 14px; @@ -182,22 +182,22 @@ } } - .card-footer { + .cardFooter { padding: 12px 15px 0; - .footer-divider { + .footerDivider { height: 0.5px; background: rgba(0, 0, 0, 0.08); margin-bottom: 12px; width: 100%; } - .footer-action { + .footerAction { display: flex; justify-content: space-between; align-items: center; - .action-text { + .actionText { font-family: "PingFang SC"; font-weight: 600; font-size: 14px; @@ -205,7 +205,7 @@ color: rgba(0, 0, 0, 0.85); } - .action-arrow { + .actionArrow { position: relative; .img { @@ -221,13 +221,13 @@ } // 到底了提示 - .bottom-tip { + .bottomTip { display: flex; justify-content: center; align-items: center; padding: 24px 0 12px; - .tip-text { + .tipText { font-family: "PingFang SC"; font-weight: 400; font-size: 14px; @@ -238,7 +238,7 @@ } // 悬浮新建消息按钮 - .floating-button { + .floatingButton { position: fixed; right: 12px; bottom: 132px; @@ -262,7 +262,7 @@ transform: scale(0.92); } - .button-icon { + .buttonIcon { width: 36px; height: 36px; position: relative; diff --git a/src/main_pages/components/MessagePageContent.tsx b/src/main_pages/components/MessagePageContent.tsx index 2af26fa..e868e44 100644 --- a/src/main_pages/components/MessagePageContent.tsx +++ b/src/main_pages/components/MessagePageContent.tsx @@ -7,7 +7,7 @@ import Taro from "@tarojs/taro"; import { useGlobalState } from "@/store/global"; import { navigateTo } from "@/utils/navigation"; import { useReddotInfo, useFetchReddotInfo } from "@/store/messageStore"; -import "@/other_pages/message/index.scss"; +import styles from "./MessagePageContent.module.scss"; interface MessageItem { id: string; @@ -127,49 +127,49 @@ const MessagePageContent = () => { }; return ( - - + + handleTabClick("comment")} > - + {(reddotInfo?.comment_unread_count || 0) > 0 && ( - - {(reddotInfo?.comment_unread_count || 0) > 99 ? '99+' : reddotInfo?.comment_unread_count} + + {`+${(reddotInfo?.comment_unread_count || 0) > 99 ? 99 : reddotInfo?.comment_unread_count}`} )} - 评论和回复 + 评论和回复 handleTabClick("follow")} > - + {(reddotInfo?.follow_unread_count || 0) > 0 && ( - - {(reddotInfo?.follow_unread_count || 0) > 99 ? '99+' : reddotInfo?.follow_unread_count} + + {`+${(reddotInfo?.follow_unread_count || 0) > 99 ? 99 : reddotInfo?.follow_unread_count}`} )} - 新增关注 + 新增关注 { onRefresherRefresh={handleRefresh} > {filteredMessages.length > 0 ? ( - + {filteredMessages.map((message) => ( - handleViewDetail(message)}> - - {message.title} + handleViewDetail(message)}> + + {message.title} - - {formatRelativeTime(message.create_time)} + + {formatRelativeTime(message.create_time)} - - {message.content} + + {message.content} - - - - 查看详情 - - + + + + 查看详情 + + ))} {filteredMessages.length > 0 && ( - - 到底了 + + 到底了 )} diff --git a/src/main_pages/components/MyselfPageContent.module.scss b/src/main_pages/components/MyselfPageContent.module.scss new file mode 100644 index 0000000..30feaf4 --- /dev/null +++ b/src/main_pages/components/MyselfPageContent.module.scss @@ -0,0 +1,393 @@ +@use "../../scss/common.scss" as *; + +// 背景渐变过渡动画 +@keyframes backgroundGradient { + 0% { + background: #ffffff; + } + 100% { + background: radial-gradient( + circle at 50% 0, + /* 光晕圆心在顶部中间 */ rgba(191, 255, 239, 0.9) 0px, + /* 中间更深的浅蓝 */ rgba(191, 255, 239, 0.5) 200px, + /* 100px 处开始淡化 */ #fafafa 300px, + /* 到 200px 变成白色 */ #fafafa 100% /* 200px 以下全白 */ + ); + } +} + +// 个人页面样式 +.myselfPage { + height: 100vh; + position: relative; + overflow-y: auto; + box-sizing: border-box; + background-color: #fafafa; + + &::-webkit-scrollbar { + display: none; + width: 0; + height: 0; + color: transparent; + } +} + +// MyselfPageContent 组件使用的类名 +.myselfPageContentMain { + animation: backgroundGradient 0s ease-in-out forwards; + z-index: 5; + flex: 1; + margin-top: 0; + box-sizing: border-box; + padding-bottom: 15px; + // padding-top 由内联样式控制 + + // 用户信息区域 + .userInfoSection { + display: flex; + flex-direction: column; + gap: 16px; + padding: 0 15px; + + // 加载状态 + .loadingContainer { + display: flex; + justify-content: center; + align-items: center; + padding: 40px 0; + + .loadingText { + @include text-style(16px, 400, 1.4em); + color: $color-primary-lightest; + } + } + + // 统计数据 + .statsSection { + display: flex; + justify-content: flex-start; + align-items: center; + + .statsContainer { + display: flex; + align-items: center; + gap: 20px; + + .statItem { + display: flex; + flex-direction: column; + align-items: center; + + .statNumber { + @include text-medium; + } + + .statLabel { + @include text-small; + } + } + } + } + + // 标签和简介 + .tagsBioSection { + display: flex; + flex-direction: column; + gap: 10px; + + .tagsContainer { + display: flex; + gap: 8px; + flex-wrap: wrap; + + .tagItem { + @include tag-base; + + .tagIcon { + width: 12px; + height: 12px; + } + + .tagText { + @include text-tag; + } + } + } + + .bioText { + @include text-body; + white-space: pre-line; + } + } + + // 球局订单和收藏功能 + .quickActionsSection { + margin-bottom: 16px; + + .actionCard { + @include card-base; + border-radius: 12px; + display: flex; + align-items: center; + overflow: hidden; + height: 48px; + + .actionContent { + height: 100%; + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + transition: background-color 0.3s ease; + + .actionIcon { + width: 20px; + height: 20px; + } + + .actionText { + @include text-style(15px, 600, 1.4em); + color: $color-primary; + } + } + + .actionDivider { + width: 1px; + height: 16px; + background: $color-primary-lightest-5; + } + } + } + } + + .testEntryCardBox { + padding: 0 15px; + } + + // 球局类型标签页 + .gameTabsSection { + margin-bottom: 0; + .tabContainer { + display: flex; + gap: 16px; + padding: 12px 15px; + + .tabItem { + padding: 12px 0; + cursor: pointer; + transition: all 0.3s ease; + + .tabText { + @include text-primary; + transition: color 0.3s ease; + } + + &.active { + .tabText { + color: $color-primary; + } + } + + &:not(.active) { + .tabText { + color: $color-primary-lightest-2; + } + } + } + } + } + + // 球局列表区域 + .gameListSection { + .dateHeader { + display: flex; + align-items: center; + gap: 4px; + padding: 10px 15px; + margin-bottom: 16px; + + .dateText { + @include text-style(14px, 600, 1.4em, 2.71%); + color: $color-primary-light; + } + + .separator { + @include text-style(18px, 400, 1.4em, 2.11%); + color: $color-primary-lightest; + } + + .weekdayText { + @include text-style(14px, 600, 1.4em, 2.71%); + color: $color-primary-light; + } + } + + // 球局卡片 + .gameCards { + display: flex; + flex-direction: column; + gap: 5px; + padding: 0 5px 15px; + + .gameCard { + @include card-base; + padding: 0 0 12px; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + + &:active { + transform: scale(0.98); + } + + // 球局标题和类型 + .gameHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 15px 0; + + .gameTitle { + @include text-style(16px, 600, 1.5em); + color: $color-primary; + } + + .gameTypeIcon { + width: 16px; + height: 16px; + + .typeIcon { + width: 100%; + height: 100%; + } + } + } + + // 球局时间 + .gameTime { + padding: 6px 15px 0; + + .timeText { + @include text-caption; + } + } + + // 球局地点和类型 + .gameLocation { + display: flex; + align-items: center; + gap: 2px; + padding: 4px 15px 0; + + .locationText, + .typeText, + .distanceText { + @include text-caption; + } + + .separator { + @include text-style(14px, 400, 1.3em); + color: $color-text-tertiary; + } + } + + // 球局图片 + .gameImages { + position: absolute; + top: 11px; + right: 5px; + width: 100px; + height: 100px; + box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.2); + + .gameImage { + position: absolute; + width: 56.44px; + height: 56.44px; + border-radius: 9px; + border: 1.5px solid $color-white; + + &:nth-child(1) { + top: 4.18px; + left: 19.18px; + } + + &:nth-child(2) { + top: 26.5px; + left: 38px; + width: 61.86px; + height: 61.86px; + } + + &:nth-child(3) { + top: 32.5px; + left: 0; + width: 62.04px; + height: 62.04px; + } + } + } + + // 球局信息标签 + .gameTags { + display: flex; + flex-direction: row; + gap: 6px; + padding: 8px 15px 0; + + .participantsInfo { + display: flex; + gap: 4px; + + .avatars { + display: flex; + align-items: center; + gap: -8px; + + .participantAvatar { + @include avatar-base(20px); + border: 1px solid $color-white; + } + } + + .participantsCount { + @include tag-base; + padding: 6px; + + .countText { + @include text-tag; + } + } + } + + .gameInfoTags { + display: flex; + gap: 4px; + + .infoTag { + @include tag-base; + + .tagText { + @include text-tag; + } + } + } + } + } + } + } + + .endedGameText { + font-family: "PingFang SC"; + font-weight: 600; + font-size: 20px; + line-height: 1.4em; + letter-spacing: 1.9%; + color: rgba(0, 0, 0, 0.85); + transition: color 0.3s ease; + padding: 24px 15px; + } +} diff --git a/src/main_pages/components/MyselfPageContent.tsx b/src/main_pages/components/MyselfPageContent.tsx index 108f631..b1c4422 100644 --- a/src/main_pages/components/MyselfPageContent.tsx +++ b/src/main_pages/components/MyselfPageContent.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useCallback } from "react"; import { View, Text, Image, ScrollView } from "@tarojs/components"; import Taro from "@tarojs/taro"; -import "@/user_pages/myself/index.scss"; +import styles from "./MyselfPageContent.module.scss"; import { UserInfoCard } from "@/components/UserInfo/index"; import { UserService } from "@/services/userService"; import ListContainer from "@/container/listContainer"; @@ -139,12 +139,12 @@ const MyselfPageContent: React.FC = () => { }; return ( - + - + { on_follow={handle_follow} onTab={handleOnTab} /> - - - + + + - 球局订单 + 球局订单 - - + + - 钱包 + 钱包 - + - - + + setActiveTab("hosted")} > - 我主办的 + 我主办的 setActiveTab("participated")} > - 我参与的 + 我参与的 - + { - 往期球局 - + 往期球局 + { - const [activeTab, setActiveTab] = useState(null); - const [messageList, setMessageList] = useState([]); - const [loading, setLoading] = useState(false); - const [reachedBottom, setReachedBottom] = useState(false); - const [refreshing, setRefreshing] = useState(false); - - // 从 store 获取红点信息 - const reddotInfo = useReddotInfo(); - const fetchReddotInfo = useFetchReddotInfo(); - - // 获取消息列表 - 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.showToast({ - title: "获取列表失败,请重试", - icon: "none", - duration: 2000, - }); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - getNoticeList(); - fetchReddotInfo(); - }, []); - - // 每次页面显示时刷新红点信息 - useDidShow(() => { - fetchReddotInfo(); - }); - - // 过滤系统消息 - const filteredMessages = messageList; - - // 处理分类标签点击 - const handleTabClick = (tab: MessageCategory) => { - // 点击评论标签跳转到评论和回复页面 - if (tab === "comment") { - Taro.navigateTo({ - url: "/other_pages/comment_reply/index", - }); - return; - } - - // 点击关注标签跳转到新增关注页面 - if (tab === "follow") { - Taro.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.navigateTo({ - url: message.jump_url, - }).catch(() => { - Taro.showToast({ - title: "页面不存在", - icon: "none", - duration: 2000, - }); - }); - }; - - // 处理滚动到底部 - const handleScrollToLower = () => { - if (!reachedBottom && filteredMessages.length > 0) { - setReachedBottom(true); - // 2秒后隐藏提示 - 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.showToast({ - title: "刷新失败", - icon: "none", - duration: 2000, - }); - } finally { - setRefreshing(false); - } - }; - - - return ( - - {/* 顶部导航栏 */} - - - {/* 分类标签 */} - - handleTabClick("comment")} - > - - - {(reddotInfo?.comment_unread_count || 0) > 0 && ( - - {(reddotInfo?.comment_unread_count || 0) > 99 ? '+99' : `+${reddotInfo?.comment_unread_count || 0}`} - - )} - - 评论和回复 - - handleTabClick("follow")} - > - - - {(reddotInfo?.follow_unread_count || 0) > 0 && ( - - {(reddotInfo?.follow_unread_count || 0) > 99 ? '+99' : `+${reddotInfo?.follow_unread_count || 0}`} - - )} - - 新增关注 - - - - {/* 系统消息卡片列表 */} - - {filteredMessages.length > 0 ? ( - - {filteredMessages.map((message) => ( - handleViewDetail(message)}> - - {message.title} - - - {formatRelativeTime(message.create_time)} - - - {message.content} - - - - - 查看详情 - - - - - - - - - ))} - {/* 到底了提示 */} - {filteredMessages.length > 0 && ( - - 到底了 - - )} - - ) : ( - - )} - - - {/* 底部导航 */} - - - ); -}; - -export default withAuth(Message); \ No newline at end of file diff --git a/src/store/README.md b/src/store/README.md index 6ba2d4c..341f146 100644 --- a/src/store/README.md +++ b/src/store/README.md @@ -179,13 +179,6 @@ interface UserStats { ## 使用示例 -查看 `src/game_pages/list/index.tsx` 获取完整的使用示例,包括: - -- 用户信息展示 -- 统计数据实时更新 -- API 请求模拟 -- 加载状态管理 -- 数据持久化演示 ## 扩展建议