diff --git a/src/components/CustomNavbar/index.tsx b/src/components/CustomNavbar/index.tsx index 5ad3864..64a7954 100644 --- a/src/components/CustomNavbar/index.tsx +++ b/src/components/CustomNavbar/index.tsx @@ -9,12 +9,12 @@ interface IProps { const CustomNavbar = (props: IProps) => { const { children } = props; const { statusNavbarHeightInfo } = useGlobalState(); - const { totalHeight } = statusNavbarHeightInfo; + const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo; return ( {children} diff --git a/src/components/DistanceQuickFilter/index.scss b/src/components/DistanceQuickFilter/index.scss index ebd5258..6922682 100644 --- a/src/components/DistanceQuickFilter/index.scss +++ b/src/components/DistanceQuickFilter/index.scss @@ -1,5 +1,6 @@ .distanceQuickFilterWrap { width: 100%; + --nutui-menu-bar-line-height: 30px; .nut-menu-bar { background-color: unset; @@ -35,19 +36,6 @@ border-bottom-right-radius: 30px; } - // .nut-menu-container-item { - // color: rgba(60, 60, 67, 0.6); - // font-size: 14px; - // font-weight: 600; - // line-height: 20px; - // } - - // .nut-menu-container-item.active { - // flex-direction: row-reverse; - // justify-content: space-between; - // color: #000; - // } - .positionWrap { display: flex; align-items: center; diff --git a/src/components/DistanceQuickFilter/index.tsx b/src/components/DistanceQuickFilter/index.tsx index 6c48e54..00583cf 100644 --- a/src/components/DistanceQuickFilter/index.tsx +++ b/src/components/DistanceQuickFilter/index.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useRef, useState } from "react"; -import { Menu, Button } from "@nutui/nutui-react-taro"; -import { Image, View, Text } from "@tarojs/components"; +import { useRef, useState } from "react"; +import { Menu } from "@nutui/nutui-react-taro"; +import { Image, View } from "@tarojs/components"; import img from "@/config/images"; import Bubble from "../Bubble"; import "./index.scss"; -const Demo3 = (props) => { +const DistanceQuickFilter = (props) => { const { cityOptions, quickOptions, @@ -49,6 +49,7 @@ const Demo3 = (props) => { }; return ( + @@ -105,6 +106,7 @@ const Demo3 = (props) => { + ); }; -export default Demo3; +export default DistanceQuickFilter; diff --git a/src/components/FilterPopup/index.tsx b/src/components/FilterPopup/index.tsx index 6986f6e..74fc113 100644 --- a/src/components/FilterPopup/index.tsx +++ b/src/components/FilterPopup/index.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { Popup } from "@nutui/nutui-react-taro"; import Range from "../../components/Range"; import Bubble from "../../components/Bubble"; @@ -29,7 +29,7 @@ const FilterPopup = (props: FilterPopupProps) => { } = props; const store = useListStore() || {}; const { getDictionaryValue } = useDictionaryActions() || {}; - const { timeBubbleData, gamesNum, dateRangeOptions } = store; + const { timeBubbleData, gamesNum } = store; /** * @description 处理字典选项 @@ -91,7 +91,7 @@ const FilterPopup = (props: FilterPopupProps) => { round visible={visible} onClose={onClose} - style={{ marginTop: statusNavbarHeigh + "px" }} + style={{ marginTop: statusNavbarHeigh + "px", maxHeight: '75vh' }} overlayStyle={{ marginTop: statusNavbarHeigh + "px" }} zIndex={1001} > diff --git a/src/components/ListCard/index.scss b/src/components/ListCard/index.scss index 55b619c..6292d27 100644 --- a/src/components/ListCard/index.scss +++ b/src/components/ListCard/index.scss @@ -269,6 +269,15 @@ display: flex; align-items: center; gap: 5px; + flex-shrink: 0; + } + + .localAreaWrapper { + max-width: 69%; + } + + .localAreaTitle { + flex-shrink: 0; } .smoothTitle { @@ -291,5 +300,11 @@ .localArea { border: 0.5px solid #FFFFFFA6; border-radius: 50%; + flex-shrink: 0; + } + .localAreaText { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } \ No newline at end of file diff --git a/src/components/ListCard/index.tsx b/src/components/ListCard/index.tsx index 166f9ca..44abf01 100644 --- a/src/components/ListCard/index.tsx +++ b/src/components/ListCard/index.tsx @@ -18,7 +18,21 @@ const ListCard: React.FC = ({ image_list = [], court_type, key, + participants, // 参与者图片 + venue_image_list, // 场馆图片 + venue_description, + game_type, // 球局类型 }) => { + + // 参与者要前三个数据 + const participantsImageList = participants?.slice(0, 3) || []; + // 场地 要第一个数据 + // const venueImageList = venue_image_list?.slice(0, 1) || []; + const venueImage = venue_image_list?.[0]?.url || ''; + + // 是否显示畅打球局 + const isShowSmoothPlayingGame = game_type === '畅打球局'; + const renderItemImage = (src: string) => { return ( = ({ }; const handleViewDetail = () => { - console.log("id", id); Taro.navigateTo({ url: `/game_pages/detail/index?id=${id || 1}&from=list`, }); @@ -120,11 +133,11 @@ const ListCard: React.FC = ({ - {Array.from({ length: 3 }).map((_, index) => ( + {(participantsImageList || []).map((item, index) => ( @@ -157,24 +170,24 @@ const ListCard: React.FC = ({ {renderImages()} {/* 畅打球局 */} - + {isShowSmoothPlayingGame && - 畅打球局 + {game_type} - 场馆方: + 场馆方: - 仁恒河滨花园网球场 + {venue_description} - + } ); }; diff --git a/src/container/listContainer/index.tsx b/src/container/listContainer/index.tsx index 055ad36..21dbe7b 100644 --- a/src/container/listContainer/index.tsx +++ b/src/container/listContainer/index.tsx @@ -2,9 +2,8 @@ import { View } from "@tarojs/components"; import ListCard from "@/components/ListCard"; import ListLoadError from "@/components/ListLoadError"; import ListCardSkeleton from "@/components/ListCardSkeleton"; -// import { VirtualList } from '@nutui/nutui-react-taro' -import "./index.scss"; import { useReachBottom } from "@tarojs/taro"; +import "./index.scss"; const ListContainer = (props) => { const { @@ -12,14 +11,12 @@ const ListContainer = (props) => { data = [], error, reload, - recommendList, + // recommendList, loadMoreMatches, } = props; - console.log("===data", data); useReachBottom(() => { - console.log("触底了"); - // 调用 store 的加载更多方法 + // 加载更多方法 loadMoreMatches(); }); @@ -39,17 +36,6 @@ const ListContainer = (props) => { // 渲染列表 const renderList = (list) => { - // 请求未回来显示骨架屏 - // if (loading && list?.length === 0) { - // return ( - // <> - // {new Array(10).fill(0).map(() => { - // return ; - // })} - // - // ); - // } - // 请求数据为空 if (!loading && list?.length === 0) { return ; @@ -58,15 +44,6 @@ const ListContainer = (props) => { // 渲染数据 return ( <> - {/* { - return - }} - /> */} {list?.map((match, index) => ( ))} @@ -76,15 +53,25 @@ const ListContainer = (props) => { return ( - {renderList(data)} - {/* 显示骨架屏 */} - {loading && renderSkeleton()} - {/* + {/* */} + {renderList(data)} + {/* 显示骨架屏 */} + {loading && renderSkeleton()} + {/* 搜索结果较少,已为你推荐其他内容 {renderList(recommendList)} */} - {/* 到底了 */} - {data?.length > 0 && 到底了} + {/* 到底了 */} + {data?.length > 0 && 到底了} + {/* */} ); }; diff --git a/src/container/listCustomNavbar/index.scss b/src/container/listCustomNavbar/index.scss index 6698e8f..8f37ff2 100644 --- a/src/container/listCustomNavbar/index.scss +++ b/src/container/listCustomNavbar/index.scss @@ -113,28 +113,31 @@ width: 100%; height: 100%; - /* 过渡动画设置,实现平滑切换 */ - transition: opacity 0.5s ease, transform 0.5s ease; + /* 启用硬件加速 */ + will-change: opacity, transform; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + + /* 优化过渡动画,使用更快的缓动函数 */ + transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* 第一个元素样式 */ .firstElement { - // background-color: #4a90e2; } /* 第二个元素样式 */ .secondElement { - // background-color: #5cb85c; /* 初始状态:透明且稍微偏移 */ opacity: 0; - // transform: translateY(20px); + transform: translateY(10px); } /* 隐藏状态 */ .hidden { opacity: 0; - transform: translateY(20px); - // pointer-events: none; /* 隐藏时不响应鼠标事件 */ + transform: translateY(10px); } /* 可见状态 */ diff --git a/src/container/listCustomNavbar/index.tsx b/src/container/listCustomNavbar/index.tsx index 9d4e8da..11fc502 100644 --- a/src/container/listCustomNavbar/index.tsx +++ b/src/container/listCustomNavbar/index.tsx @@ -2,7 +2,6 @@ import { View, Text, Image } from "@tarojs/components"; import img from "@/config/images"; import { useGlobalState } from "@/store/global"; import { useUserInfo, } from '@/store/userStore' - import { useListState } from "@/store/listStore"; import CustomNavbar from "@/components/CustomNavbar"; import { Input } from "@nutui/nutui-react-taro"; @@ -26,13 +25,11 @@ const ListHeader = (props: IProps) => { leftIconClick, } = config || {}; const { - location, - getLocationText, getLocationLoading, statusNavbarHeightInfo, } = useGlobalState(); const { gamesNum, searchValue, isShowInputCustomerNavBar } = useListState(); - const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo; + const { navbarHeight } = statusNavbarHeightInfo; const { city,district } = useUserInfo() @@ -68,6 +65,11 @@ const ListHeader = (props: IProps) => { } }; + const navbarStyle = { + height: `${navbarHeight}px`, + paddingTop: `4px`, + }; + return ( @@ -75,10 +77,7 @@ const ListHeader = (props: IProps) => { ); }; + export default ListHeader; diff --git a/src/game_pages/list/index.module.scss b/src/game_pages/list/index.module.scss index e654073..2c2aa68 100644 --- a/src/game_pages/list/index.module.scss +++ b/src/game_pages/list/index.module.scss @@ -18,6 +18,9 @@ padding-top: 10px; padding-bottom: 10px; gap: 5px; + position: sticky; + background-color: #fff; + z-index: 100; } .menuFilter { diff --git a/src/game_pages/list/index.tsx b/src/game_pages/list/index.tsx index 6a8fe2e..417a5ec 100644 --- a/src/game_pages/list/index.tsx +++ b/src/game_pages/list/index.tsx @@ -1,7 +1,7 @@ import SearchBar from "@/components/SearchBar"; import FilterPopup from "@/components/FilterPopup"; import styles from "./index.module.scss"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import Taro, { usePageScroll } from "@tarojs/taro"; import { useListStore } from "@/store/listStore"; import { useGlobalState } from "@/store/global"; @@ -12,11 +12,6 @@ import ListContainer from "@/container/listContainer"; import DistanceQuickFilter from "@/components/DistanceQuickFilter"; import { withAuth } from "@/components"; import { updateUserLocation } from "@/services/userService"; -// import img from "@/config/images"; -// import ShareCardCanvas from "@/components/ShareCardCanvas/example"; - - - const ListPage = () => { @@ -46,23 +41,28 @@ const ListPage = () => { isShowInputCustomerNavBar, initialFilterSearch, loadMoreMatches, - dateRangeOptions + fetchGetGamesCount } = store; + // 简化的滚动处理函数 usePageScroll((res) => { - if (res?.scrollTop >= totalHeight) { - !isShowInputCustomerNavBar && + const shouldShowInputNav = res?.scrollTop >= totalHeight; + + // 只有当状态需要改变时才更新 + if (shouldShowInputNav && !isShowInputCustomerNavBar) { updateState({ isShowInputCustomerNavBar: true }); - } else { - isShowInputCustomerNavBar && + } else if (!shouldShowInputNav && isShowInputCustomerNavBar) { updateState({ isShowInputCustomerNavBar: false }); } }); + const scrollContextRef = useRef(null) + useEffect(() => { getLocation() }, []); + // 监听距离和排序方式变化,自动调用接口 useEffect(() => { // 只有当 distanceQuickFilter 有值时才调用接口 @@ -89,6 +89,7 @@ const ListPage = () => { console.error('更新用户位置失败:', error); } } + fetchGetGamesCount(); // 页面加载时获取数据 getMatchesData(); @@ -104,35 +105,45 @@ const ListPage = () => { // } // 下拉刷新处理函数 - 使用Taro生命周期钩子 - Taro.usePullDownRefresh(() => { - console.log("===触发下拉刷新"); - clearFilterOptions() + Taro.usePullDownRefresh(async () => { + try { + // 调用刷新方法 + await refreshMatches(); - // 调用 store 的刷新方法 - // refreshMatches() - // .then(() => { - // // 刷新完成后停止下拉刷新动画 - // Taro.stopPullDownRefresh(); + // 刷新完成后停止下拉刷新动画 + Taro.stopPullDownRefresh(); - // // 显示刷新成功提示 - // Taro.showToast({ - // title: "刷新成功", - // icon: "success", - // duration: 1500, - // }); - // }) - // .catch(() => { - // // 刷新失败时也停止动画 - // Taro.stopPullDownRefresh(); + // 显示刷新成功提示 + Taro.showToast({ + title: "刷新成功", + icon: "success", + duration: 1500, + }); + } catch (error) { + // 刷新失败时也停止动画 + Taro.stopPullDownRefresh(); - // Taro.showToast({ - // title: "刷新失败", - // icon: "error", - // duration: 1500, - // }); - // }); + Taro.showToast({ + title: "刷新失败,请重试", + icon: "error", + duration: 1500, + }); + } }); + /** + * @description 综合筛选确认 + * @returns + */ + const handleFilterConfirm = () => { + toggleShowPopup(); + getMatchesData(); + } + + /** + * @description 综合筛选弹框 + * @returns + */ const toggleShowPopup = () => { updateState({ isShowFilterPopup: !isShowFilterPopup }); }; @@ -164,9 +175,10 @@ const ListPage = () => { }; return ( - + {/* 自定义导航 */} + {/* */} { { )} {/* 筛选 */} - + { loadMoreMatches={loadMoreMatches} /> - ); diff --git a/src/game_pages/searchResult/index.tsx b/src/game_pages/searchResult/index.tsx index e368f5f..0009454 100644 --- a/src/game_pages/searchResult/index.tsx +++ b/src/game_pages/searchResult/index.tsx @@ -38,16 +38,9 @@ const SearchResult = () => { const pages = Taro.getCurrentPages() const currentPage = pages?.[pages.length - 1]; updateState({currentPage, isSearchResult: true}) - // if (location?.address) { - // 保存位置 - // updateState({ location }); - // 页面加载时获取数据 - console.log('===搜索结果页===') getMatchesData(); - // } return () => { - console.log('===搜索结果组件卸载') updateState({currentPage: '', isSearchResult: false}) } }, []); diff --git a/src/services/listApi.ts b/src/services/listApi.ts index 47fc5fb..4b9abd4 100644 --- a/src/services/listApi.ts +++ b/src/services/listApi.ts @@ -46,6 +46,20 @@ export const getGamesIntegrateList = async (params?: Record) => { } }; +/** + * 获取列表数量 + * @param params + * @returns + */ +export const getGamesCount = async (params?: Record) => { + try { + return httpService.post('/games/count', params, { showLoading: true }) + } catch (error) { + console.error("列表数量获取失败:", error); + throw error; + } +}; + /** * 获取搜索历史记录的异步函数 * @param {Object} params - 查询参数对象 diff --git a/src/store/listStore.ts b/src/store/listStore.ts index d48640e..e281fd7 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -6,6 +6,7 @@ import { getSearchHistory, clearHistory, searchSuggestion, + getGamesCount, } from "../services/listApi"; import { ListActions, @@ -14,12 +15,10 @@ import { IPayload, } from "../../types/list/types"; -import dateRangeUtils from '@/utils/dateRange' // 完整的 Store 类型 type TennisStore = ListState & ListActions; -const toDate = dateRangeUtils?.formatDate(new Date()) const defaultDateRange: [string, string] = [dayjs().format('YYYY-MM-DD'), dayjs().add(1, 'M').format('YYYY-MM-DD')] const defaultFilterOptions: IFilterOptions = { @@ -100,6 +99,8 @@ export const useListStore = create()((set, get) => ({ ], // 球局数量 gamesNum: 0, + // 是否还有更多数据 + isHasMoreData: true, // 页面滚动距离顶部距离 是否大于0 isScrollTop: false, // 搜索历史数据 @@ -143,22 +144,31 @@ export const useListStore = create()((set, get) => ({ lat: state?.location?.latitude, lng: state?.location?.longitude, }; - console.log('===列表参数params', params) return params; }, // 设置搜索的列表结果 - setListData: (payload: IPayload) => { + setListData: (payload: IPayload & { isAppend?: boolean }) => { const { isSearchResult } = get(); - const { error, data, loading, gamesNum } = payload; + const { error, data, loading, count, isAppend = false } = payload; const saveKey = isSearchResult ? "searchResultData" : "matches"; - const saveData = { error, loading, gamesNum, [saveKey]: data }; - console.log('===saveData', saveData) - set({...saveData}); + const isHasMoreData = count > 0; + + if (isAppend) { + // 追加数据到现有数组 + const currentData = get()[saveKey] || []; + const newData = [...currentData, ...(data || [])]; + const saveData = { error, loading, count, isHasMoreData, [saveKey]: newData }; + set({...saveData}); + } else { + // 替换整个数组 + const saveData = { error, loading, count, isHasMoreData, [saveKey]: data }; + set({...saveData}); + } }, // 获取列表数据(常规搜索) - fetchMatches: async (params, isFirstLoad = false) => { + fetchMatches: async (params, isFirstLoad = false, isAppend = false) => { set({ loading: true, error: null }); const { getSearchParams, setListData, distanceQuickFilter } = get(); @@ -191,70 +201,45 @@ export const useListStore = create()((set, get) => ({ error: "-1", data: [], loading: false, - gamesNum: 0, + count: 0, + isAppend, }); + return Promise.reject(new Error('获取数据失败')); } const { count, rows } = data; setListData({ error: '', data: rows || [], loading: false, - gamesNum: count, + count, + isAppend, }); + return Promise.resolve(); } catch (error) { setListData({ error: "-1", data: [], loading: false, - gamesNum: 0, + count: 0, + isAppend, }); + return Promise.reject(error); } }, - // 获取列表数据(智能筛选) - // getIntegrateListData: async (params) => { - // set({ loading: true, error: null }); - // const { getSearchParams, setListData } = get(); - // try { - // const searchParams = getSearchParams() || {}; - // const reqParams = { - // ...(searchParams || {}), - // ...params, - // }; - // reqParams.order = ""; - // console.log("===getGamesIntegrateList 获取列表数据参数:", reqParams); - // const resData = (await getGamesIntegrateList(reqParams)) || {}; - // const { data = {}, code } = resData; - // if (code !== 0) { - // setListData({ - // error: '-1', - // data: [], - // loading: false, - // gamesNum: 0, - // }); - // } - // const { count, rows } = data; - // setListData({ - // // recommendList: rows || [], - // error: null, - // data: rows || [], - // loading: false, - // gamesNum: count, - // }); - // } catch (error) { - // setListData({ - // error: null, - // matches: [], - // loading: false, - // gamesNum: 0, - // }); - // } - // }, - // 获取列表数据 - getMatchesData: () => { + getMatchesData: async () => { const { fetchMatches } = get(); - fetchMatches({}, true); // 第一次进入页面,传入 isFirstLoad = true + return await fetchMatches({}, true); // 第一次进入页面,传入 isFirstLoad = true + }, + + // 获取球局数量 + fetchGetGamesCount: async () => { + const { getSearchParams } = get(); + const params = getSearchParams() || {}; + const resData = (await getGamesCount(params)) || {}; + const gamesNum = resData?.data?.count || 0; + set({ gamesNum }); }, // 获取历史搜索数据 @@ -307,7 +292,7 @@ export const useListStore = create()((set, get) => ({ // 更新综合筛选项 updateFilterOptions: (payload: Record) => { - const { filterOptions: preFilterOptions, getMatchesData } = get() || {}; + const { filterOptions: preFilterOptions, fetchGetGamesCount } = get() || {}; const filterOptions = { ...preFilterOptions, ...payload }; const filterCount = Object.values(filterOptions).filter(Boolean).length; console.log("===更新综合筛选项", filterOptions, filterCount); @@ -316,42 +301,48 @@ export const useListStore = create()((set, get) => ({ filterCount, pageOption: defaultPageOption, }); - // 重新搜索数据 - getMatchesData(); + // 获取球局数量 + fetchGetGamesCount(); }, // 清空综合筛选选项 clearFilterOptions: () => { - const { getMatchesData } = get() || {}; + const { getMatchesData, fetchGetGamesCount } = get() || {}; set({ filterOptions: defaultFilterOptions, filterCount: 0, pageOption: defaultPageOption, }); getMatchesData(); + fetchGetGamesCount(); }, // 加载更多数据 loadMoreMatches: () => { - const { pageOption, getMatchesData } = get() || {}; + const { pageOption, fetchMatches, isHasMoreData } = get() || {}; + if (!isHasMoreData) { + return; + } set({ pageOption: { page: pageOption?.page + 1, pageSize: 20, }, }); - getMatchesData(); + // 加载更多时追加数据到现有数组 + fetchMatches({}, false, true); }, // 初始化搜索条件 重新搜索 - initialFilterSearch: () => { - const { getMatchesData } = get(); + initialFilterSearch: async () => { + const { getMatchesData, fetchGetGamesCount } = get(); set({ distanceQuickFilter: defaultDistanceQuickFilter, filterOptions: defaultFilterOptions, pageOption: defaultPageOption, }); - getMatchesData(); + fetchGetGamesCount(); + return await getMatchesData(); }, // 更新store数据 diff --git a/types/list/types.ts b/types/list/types.ts index ede3572..681c8d9 100644 --- a/types/list/types.ts +++ b/types/list/types.ts @@ -45,6 +45,7 @@ export interface ListState { timeBubbleData: BubbleOption[] dateRangeOptions: BubbleOption[] gamesNum: number + isHasMoreData: boolean isScrollTop: boolean searchHistoryParams: Record searchHistory: {id: number, title: string}[] @@ -61,7 +62,7 @@ export interface ListState { } export interface ListActions { - fetchMatches: (params?: Record,isFirstLoad?: Boolean) => Promise + fetchMatches: (params?: Record, isFirstLoad?: Boolean, isAppend?: boolean) => Promise // getIntegrateListData: (params?: Record) => Promise getMatchesData: () => void clearError: () => void @@ -75,13 +76,15 @@ export interface ListActions { loadMoreMatches: () => void initialFilterSearch: () => void setListData: (payload: IPayload) => void + fetchGetGamesCount: () => Promise } export interface IPayload { error: string; data: TennisMatch[]; loading: boolean; - gamesNum: number; + count: number; + isAppend?: boolean; } // 快捷筛选 @@ -178,4 +181,13 @@ export interface ListCardProps { shinei: string; showSkeleton?: boolean; key?: string + participants: { + avatar_url: string; + }[]; + venue_image_list: { + url: string; + }[]; + venue_description: string; + game_type: string; + type: string; } \ No newline at end of file