列表搜索页面

This commit is contained in:
juguohong
2025-09-06 22:08:34 +08:00
parent c78be00ded
commit 2d0d728969
15 changed files with 518 additions and 128 deletions

View File

@@ -52,7 +52,6 @@ const Bubble: React.FC<BubbleProps> = ({
); );
onChange(name, newSelectedValues, selectedOptions); onChange(name, newSelectedValues, selectedOptions);
} else { } else {
console.log('===111', name, option.value)
onChange(name, option.value, option); onChange(name, option.value, option);
} }
} }

View File

@@ -19,11 +19,12 @@ const Demo3 = (props) => {
const itemRef = useRef(null); const itemRef = useRef(null);
const handleChange = (name: string, value: string) => { const handleChange = (name: string, value: string) => {
// setIsChange(true);
onChange && onChange(name, value); onChange && onChange(name, value);
(itemRef.current as any)?.toggle(false); (itemRef.current as any)?.toggle(false);
}; };
// const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
return ( return (
<Menu <Menu
className="distanceQuickFilterWrap" className="distanceQuickFilterWrap"

View File

@@ -1,13 +1,10 @@
import { Popup } from "@nutui/nutui-react-taro"; import { Popup } from "@nutui/nutui-react-taro";
import Range from "../../components/Range"; import Range from "../../components/Range";
import Bubble from "../../components/Bubble"; import Bubble from "../../components/Bubble";
import styles from "./filterPopup.module.scss"; import styles from "./index.module.scss";
import TitleComponent from "@/components/Title";
import { Button } from "@nutui/nutui-react-taro"; import { Button } from "@nutui/nutui-react-taro";
import { Image } from "@tarojs/components";
import img from "../../config/images";
import { useListStore } from "src/store/listStore"; import { useListStore } from "src/store/listStore";
import { FilterPopupProps } from "../../../types/list/types"; import { BubbleOption, FilterPopupProps } from "../../../types/list/types";
// 场地 // 场地
import CourtType from "@/components/CourtType"; import CourtType from "@/components/CourtType";
// 玩法 // 玩法
@@ -37,7 +34,7 @@ const FilterPopup = (props: FilterPopupProps) => {
}; };
const courtType = getDictionaryValue("court_type") || []; const courtType = getDictionaryValue("court_type") || [];
const locationOptions = useMemo(() => { const locationOptions: BubbleOption[] = useMemo(() => {
return courtType ? handleOptions(courtType) : []; return courtType ? handleOptions(courtType) : [];
}, [courtType]); }, [courtType]);
@@ -108,16 +105,16 @@ const FilterPopup = (props: FilterPopupProps) => {
{/* CourtType */} {/* CourtType */}
<CourtType <CourtType
onChange={handleFilterChange} onChange={handleFilterChange}
name="court_type" name="venueType"
options={locationOptions} options={locationOptions}
value={filterOptions?.site} value={filterOptions?.venueType}
/> />
{/* 玩法 */} {/* 玩法 */}
<GamePlayType <GamePlayType
onChange={handleFilterChange} onChange={handleFilterChange}
name="game_play" name="playType"
options={gamePlayOptions} options={gamePlayOptions}
value={filterOptions?.game_play} value={filterOptions?.playType}
/> />
{/* 按钮 */} {/* 按钮 */}
<div className={styles.filterPopupBtnWrapper}> <div className={styles.filterPopupBtnWrapper}>

View File

@@ -8,14 +8,14 @@ const ListCard: React.FC<ListCardProps> = ({
id, id,
title, title,
dateTime, dateTime,
location, venue_description,
distance, distance_km,
registeredCount, registeredCount,
maxCount, maxCount,
skillLevel, skillLevel,
matchType, play_type,
images = [], images = [],
shinei, court_type,
}) => { }) => {
const renderItemImage = (src: string) => { const renderItemImage = (src: string) => {
return <Image src={src} className="image" mode="aspectFill" />; return <Image src={src} className="image" mode="aspectFill" />;
@@ -64,17 +64,16 @@ const ListCard: React.FC<ListCardProps> = ({
{/* 左侧内容区域 */} {/* 左侧内容区域 */}
<View className="content"> <View className="content">
{/* 标题 */} {/* 标题 */}
<View className="titleWrapper"> {title && <View className="titleWrapper">
<Text className="title">{title}</Text> <Text className="title">{title}</Text>
<Image <Image
src={img.ICON_LIST_RIGHT_ARROW} src={img.ICON_LIST_RIGHT_ARROW}
className="title-right-arrow" className="title-right-arrow"
mode="aspectFit" mode="aspectFit"
/> />
</View> </View>}
{/* 时间信息 */} {/* 时间信息 */}
<View className="date-time"> <View className="date-time">
<Text>{dateTime}</Text> <Text>{dateTime}</Text>
</View> </View>
@@ -82,10 +81,11 @@ const ListCard: React.FC<ListCardProps> = ({
{/* 地点,室内外,距离 */} {/* 地点,室内外,距离 */}
<View className="location"> <View className="location">
<Text className="location-text location-position">{location}</Text> {venue_description &&
<Text className="location-text location-position">{venue_description}</Text>}
<Text className="location-text location-time-distance"> <Text className="location-text location-time-distance">
{shinei && `${shinei}`} {court_type && `${court_type}`}
{distance && `${distance}`} {distance_km && `${distance_km}`}
</Text> </Text>
</View> </View>
@@ -115,11 +115,11 @@ const ListCard: React.FC<ListCardProps> = ({
</Text> </Text>
</View> </View>
<View className="tag"> <View className="tag">
<Text className="tag-text">{skillLevel}</Text> <Text className="tag-text">{skill_level_max} zh {skill_level_max}</Text>
</View>
<View className="tag">
<Text className="tag-text">{matchType}</Text>
</View> </View>
{play_type && <View className="tag">
<Text className="tag-text">{play_type}</Text>
</View>}
</View> </View>
</View> </View>
</View> </View>

View File

@@ -21,6 +21,7 @@ const SearchBarComponent = (props: IProps) => {
return ( return (
<> <>
<SearchBar <SearchBar
disabled
clearable={false} clearable={false}
leftIn={ leftIn={
<View className={styles.searchBarLeft}> <View className={styles.searchBarLeft}>

View File

@@ -4,7 +4,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
// background-color: red; padding-bottom: 34px;
.recommendTextWrapper { .recommendTextWrapper {
display: flex; display: flex;
@@ -19,4 +19,17 @@
font-weight: 500; font-weight: 500;
line-height: 24px; line-height: 24px;
} }
.bottomTextWrapper {
width: 100%;
height: 68px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
font-weight: 500;
line-height: 24px;
}
} }

View File

@@ -3,10 +3,17 @@ import ListCard from "@/components/ListCard";
import ListLoadError from "@/components/ListLoadError"; import ListLoadError from "@/components/ListLoadError";
import ListCardSkeleton from "@/components/ListCardSkeleton"; import ListCardSkeleton from "@/components/ListCardSkeleton";
import "./index.scss"; import "./index.scss";
import { useReachBottom } from "@tarojs/taro";
const ListContainer = (props) => { const ListContainer = (props) => {
const { loading, data = [], error, reload, recommendList } = props; const { loading, data = [], error, reload, recommendList } = props;
useReachBottom(() => {
console.log("触底了");
// 调用 store 的加载更多方法
// loadMoreMatches();
});
if (error) { if (error) {
return <ListLoadError reload={reload} />; return <ListLoadError reload={reload} />;
} }
@@ -38,6 +45,8 @@ const ListContainer = (props) => {
<Text className="recommendText"></Text> <Text className="recommendText"></Text>
</View> </View>
{renderList(recommendList)} {renderList(recommendList)}
{/* 到底了 */}
<View className="bottomTextWrapper"></View>
</View> </View>
); );
}; };

View File

@@ -1,10 +1,8 @@
import Menu from "../../components/Menu";
import CityFilter from "../../components/CityFilter";
import SearchBar from "../../components/SearchBar"; import SearchBar from "../../components/SearchBar";
import FilterPopup from "./FilterPopup"; import FilterPopup from "@/components/FilterPopup";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { useEffect } from "react"; import { useEffect } from "react";
import Taro, { usePageScroll, useReachBottom } from "@tarojs/taro"; import Taro, { usePageScroll } from "@tarojs/taro";
import { useListStore } from "@/store/listStore"; import { useListStore } from "@/store/listStore";
import { useGlobalState } from "@/store/global"; import { useGlobalState } from "@/store/global";
import { View } from "@tarojs/components"; import { View } from "@tarojs/components";
@@ -19,7 +17,7 @@ const ListPage = () => {
// 从 store 获取数据和方法 // 从 store 获取数据和方法
const store = useListStore() || {}; const store = useListStore() || {};
const { statusNavbarHeightInfo } = useGlobalState() || {}; const { statusNavbarHeightInfo, location } = useGlobalState() || {};
const { totalHeight } = statusNavbarHeightInfo || {}; const { totalHeight } = statusNavbarHeightInfo || {};
const { const {
isShowFilterPopup, isShowFilterPopup,
@@ -43,25 +41,18 @@ const ListPage = () => {
} = store; } = store;
usePageScroll((res) => { usePageScroll((res) => {
// if (res?.scrollTop > 0 && !isScrollTop) {
// updateState({ isScrollTop: true });
// }
if (res?.scrollTop >= totalHeight && !isScrollTop) { if (res?.scrollTop >= totalHeight && !isScrollTop) {
updateState({ isShowInputCustomerNavBar: true }); !isShowInputCustomerNavBar && updateState({ isShowInputCustomerNavBar: true });
} else { } else {
updateState({ isShowInputCustomerNavBar: false }); isShowInputCustomerNavBar && updateState({ isShowInputCustomerNavBar: false });
} }
}); });
useReachBottom(() => {
console.log("触底了");
// 调用 store 的加载更多方法
// loadMoreMatches();
});
useEffect(() => { useEffect(() => {
// 页面加载时获取数据 // 页面加载时获取数据
fetchMatches(); fetchMatches();
// 保存位置
updateState({ location });
}, []); }, []);
// 下拉刷新处理函数 - 使用Taro生命周期钩子 // 下拉刷新处理函数 - 使用Taro生命周期钩子

View File

@@ -5,11 +5,60 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: 5px;
padding: 5px 0px 10px 0px; padding: 5px 15px 10px 15px;
position: sticky; position: sticky;
top: -1px; top: -1px;
background-color: #fefefe; background-color: #fefefe;
z-index: 123; z-index: 123;
.nut-menu-bar {
padding: 0;
}
.nut-menu-container-wrap {
left: -15px;
}
.filterIconWrapper {
display: flex;
width: 28px;
height: 28px;
justify-content: center;
align-items: center;
gap: 10px;
flex-shrink: 0;
aspect-ratio: 1/1;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: #FFF;
position: relative;
&.active {
background-color: #000000;
}
}
.filterIcon {
width: 14px;
height: 14px;
flex-shrink: 0;
}
.filterCount {
background-color: #000000;
position: absolute;
width: 15px;
height: 15px;
border: 2px solid #ffffff;
border-radius: 50%;
right: -5px;
bottom: -5px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
}
} }
.menuFilter { .menuFilter {

View File

@@ -1,34 +1,53 @@
import { View } from "@tarojs/components"; import { View, Image, Text } from "@tarojs/components";
import { useListState } from "@/store/listStore"; import { useSearchResultState } from "@/store/searchResultStore";
import { useGlobalState } from "@/store/global"; import { useGlobalState } from "@/store/global";
import ListContainer from "@/container/listContainer"; import ListContainer from "@/container/listContainer";
import img from "@/config/images";
import "./index.scss"; import "./index.scss";
import DistanceQuickFilter from "@/components/DistanceQuickFilter"; import DistanceQuickFilter from "@/components/DistanceQuickFilter";
import { useEffect } from "react"; import { useEffect } from "react";
import FilterPopup from "@/components/FilterPopup";
const SearchResult = () => { const SearchResult = () => {
const { const {
distanceData, isShowFilterPopup,
quickFilterData, error,
distanceQuickFilter, distanceQuickFilter,
updateState,
matches, matches,
recommendList, recommendList,
loading, loading,
error,
refreshMatches,
fetchMatches, fetchMatches,
} = useListState() || {}; refreshMatches,
updateState,
filterCount,
updateFilterOptions, // 更新筛选条件
filterOptions,
clearFilterOptions,
distanceData,
quickFilterData,
} = useSearchResultState() || {};
const { statusNavbarHeightInfo } = useGlobalState() || {}; const { statusNavbarHeightInfo } = useGlobalState() || {};
const { totalHeight } = statusNavbarHeightInfo || {} const { totalHeight } = statusNavbarHeightInfo || {};
const isSelect = filterCount > 0;
useEffect(() => { useEffect(() => {
// 页面加载时获取数据 // 页面加载时获取数据
fetchMatches(); fetchMatches();
}, []); }, []);
/**
* @description 更新筛选条件
* @param {Record<string, any>} params 筛选项
*/
const handleUpdateFilterOptions = (params: Record<string, any>) => {
updateFilterOptions(params);
};
const toggleShowPopup = () => {
updateState({ isShowFilterPopup: !isShowFilterPopup });
};
// 距离筛选 // 距离筛选
const handleDistanceOrQuickChange = (name, value) => { const handleDistanceOrQuickChange = (name, value) => {
updateState({ updateState({
@@ -42,9 +61,14 @@ const SearchResult = () => {
return ( return (
<View className="searchResultPage"> <View className="searchResultPage">
{/* 筛选 */} {/* 筛选 */}
<View className='searchResultFilterWrapper' style={{ <View
className="searchResultFilterWrapper"
style={
{
// top: `${totalHeight}px` // top: `${totalHeight}px`
}}> }
}
>
<DistanceQuickFilter <DistanceQuickFilter
cityOptions={distanceData} cityOptions={distanceData}
quickOptions={quickFilterData} quickOptions={quickFilterData}
@@ -54,6 +78,31 @@ const SearchResult = () => {
cityValue={distanceQuickFilter?.distance} cityValue={distanceQuickFilter?.distance}
quickValue={distanceQuickFilter?.quick} quickValue={distanceQuickFilter?.quick}
/> />
{/* 筛选 icon */}
<View className={`filterIconWrapper ${isSelect && 'active'}`} onClick={toggleShowPopup}>
<Image
src={isSelect ? img.ICON_FILTER_SELECTED : img.ICON_FILTER}
className={`filterIcon ${isSelect && 'active'}`}
/>
{isSelect && <Text className="filterCount">{filterCount}</Text>}
</View>
{/* 筛选弹框 */}
{/* 综合筛选 */}
{isShowFilterPopup && (
<View>
<FilterPopup
loading={loading}
onCancel={toggleShowPopup}
onConfirm={toggleShowPopup}
onChange={handleUpdateFilterOptions}
filterOptions={filterOptions}
onClear={clearFilterOptions}
visible={isShowFilterPopup}
onClose={toggleShowPopup}
statusNavbarHeigh={0}
/>
</View>
)}
</View> </View>
{/* 列表内容 */} {/* 列表内容 */}

View File

@@ -12,39 +12,39 @@ interface ApiResponse<T> {
} }
/** /**
* 获取网球比赛列表 * 获取列表
* @param params 查询参数 * @param params 查询参数
* @returns Promise<TennisMatch[]> * @returns Promise<TennisMatch[]>
*/ */
export const getTennisMatches = async (params?: { export const getGamesList = async (params?: {
page?: number; page?: number;
pageSize?: number; pageSize?: number;
location?: string; location?: string;
skillLevel?: string; skillLevel?: string;
}) => { }) => {
try { try {
return httpService.post('/venues/list', params, { showLoading: false }) return httpService.post('/games/list', params, { showLoading: false })
} catch (error) { } catch (error) {
console.error("列表数据获取失败:", error); console.error("列表数据获取失败:", error);
throw error; throw error;
} }
}; };
/** export const getGamesIntegrateList = async (params?: {
* 刷新网球比赛数据 page?: number;
* @returns Promise<TennisMatch[]> pageSize?: number;
*/ location?: string;
export const refreshTennisMatches = async (params) => { skillLevel?: string;
}) => {
try { try {
// 生成新的动态数据 return httpService.post('/games/integrate_list', params, { showLoading: false })
const matches = generateDynamicData(params);
return matches;
} catch (error) { } catch (error) {
console.error("API刷新失败:", error); console.error("列表数据获取失败:", error);
throw error; throw error;
} }
}; };
/** /**
* 获取搜索历史记录的异步函数 * 获取搜索历史记录的异步函数
* @param {Object} params - 查询参数对象 * @param {Object} params - 查询参数对象
@@ -69,7 +69,7 @@ export const getSearchHistory = async (params) => {
export const clearHistory = async () => { export const clearHistory = async () => {
try { try {
// 调用HTTP服务清除搜索历史记录 // 调用HTTP服务清除搜索历史记录
return httpService.post('/games/clear_history') return httpService.post('/search_history/delete_all')
} catch (error) { } catch (error) {
// 捕获并打印错误信息 // 捕获并打印错误信息
console.error("清除历史记录失败:", error); console.error("清除历史记录失败:", error);
@@ -86,7 +86,7 @@ export const clearHistory = async () => {
export const searchSuggestion = async (params) => { export const searchSuggestion = async (params) => {
try { try {
// 调用HTTP服务获取搜索建议 // 调用HTTP服务获取搜索建议
return httpService.get('/games/search_suggestion', params) return httpService.get('/games/search_recommendations', params)
} catch (error) { } catch (error) {
// 捕获并打印错误信息 // 捕获并打印错误信息
console.error("搜索建议获取失败:", error); console.error("搜索建议获取失败:", error);

View File

@@ -1,16 +1,16 @@
import { create } from 'zustand' import { create } from 'zustand'
import { getTennisMatches, getSearchHistory, clearHistory, searchSuggestion } from '../services/listApi' import { getGamesList, getGamesIntegrateList, getSearchHistory, clearHistory, searchSuggestion } from '../services/listApi'
import { ListActions, IFilterOptions, ListState } from '../../types/list/types' import { ListActions, IFilterOptions, ListState } from '../../types/list/types'
// 完整的 Store 类型 // 完整的 Store 类型
type TennisStore = ListState & ListActions type TennisStore = ListState & ListActions
const defaultFilterOptions: IFilterOptions = { const defaultFilterOptions: IFilterOptions = {
location: '', // 位置 dateRange: [], // 日期区间
time: '', // 时间 timeSlot: '', // 时间
ntrp: [1.0, 5.0], // NTRP 水平区间 ntrp: [1, 5], // NTRP 水平区间
court_type: '', // 场地类型 venueType: '', // 场地类型
game_play: '', // 玩法 playType: '', // 玩法
}; };
const defaultDistance = 'all'; // 默认距离 const defaultDistance = 'all'; // 默认距离
@@ -21,6 +21,10 @@ export const useListStore = create<TennisStore>()((set, get) => ({
matches: [], matches: [],
// 推荐列表 // 推荐列表
recommendList: [], recommendList: [],
location: {
latitude: 0,
longitude: 0,
}, // 位置
// 是否加载中 // 是否加载中
loading: false, loading: false,
error: null, error: null,
@@ -38,10 +42,10 @@ export const useListStore = create<TennisStore>()((set, get) => ({
quickFilter: 1, // 1: 默认 2: 好评 3: 销量 quickFilter: 1, // 1: 默认 2: 好评 3: 销量
// 距离筛选数据 // 距离筛选数据
distanceData: [ distanceData: [
{ id: 0, label: "全城", value: "全城" }, { id: 0, label: "全城", value: "0" },
{ id: 1, label: "3km", value: "3km" }, { id: 1, label: "3km", value: "3" },
{ id: 2, label: "5km", value: "5km" }, { id: 2, label: "5km", value: "5" },
{ id: 3, label: "10km", value: "10km" }, { id: 3, label: "10km", value: "10" },
], ],
// 快捷筛选数据 // 快捷筛选数据
quickFilterData: [ quickFilterData: [
@@ -100,13 +104,42 @@ export const useListStore = create<TennisStore>()((set, get) => ({
isOpenDistancePopup: false, isOpenDistancePopup: false,
// 打开快捷筛选框 // 打开快捷筛选框
isOpenQuickFilterPopup: false, isOpenQuickFilterPopup: false,
// 分页
pageOption: {
page: 1,
pageSize: 20,
},
// 获取比赛数据 // 组装搜索数据
getSearchParams: () => {
const state = get()
const filterOptions = state?.filterOptions || {};
const params = {
pageOption: state.pageOption,
seachOption: {
...filterOptions,
title: state.searchValue,
},
order: '',
lat: state?.location?.latitude,
lng: state?.location?.longitude,
}
return params;
},
// 初始化获取比赛数据
fetchMatches: async (params) => { fetchMatches: async (params) => {
set({ loading: true, error: null }) set({ loading: true, error: null })
try { try {
const resData = await getTennisMatches(params) || {}; const { getSearchParams } = get();
const searchParams = getSearchParams() || {};
const reqParams = {
...(searchParams || {}),
...params,
}
const resData = await getGamesList(reqParams) || {};
console.log('===resData', resData)
const { data = {}, code } = resData; const { data = {}, code } = resData;
if (code !== 0) { if (code !== 0) {
set({ set({
@@ -116,25 +149,25 @@ export const useListStore = create<TennisStore>()((set, get) => ({
}) })
} }
const { count, rows } = data; const { count, rows } = data;
const list = (rows || []).map(() => { // const list = (rows || []).map(() => {
return { // return {
id: "3", // id: "3",
title: "黄浦区双打约球", // title: "黄浦区双打约球",
dateTime: "7月20日(周日)下午6点 2小时", // dateTime: "7月20日(周日)下午6点 2小时",
location: "仁恒河滨花园网球场", // location: "仁恒河滨花园网球场",
distance: "3.5km", // distance: "3.5km",
registeredCount: 3, // registeredCount: 3,
maxCount: 4, // maxCount: 4,
skillLevel: "2.0 至 2.5", // skillLevel: "2.0 至 2.5",
matchType: "双打", // matchType: "双打",
images: [ // images: [
"https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center", // "https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center",
], // ],
} // }
}) // })
set({ set({
matches: list || rows || [], matches: rows || [],
recommendList: list || rows || [], recommendList: rows || [],
error: null, error: null,
loading: false, loading: false,
gamesNum: count, gamesNum: count,
@@ -149,22 +182,6 @@ export const useListStore = create<TennisStore>()((set, get) => ({
} }
}, },
// 刷新比赛数据
refreshMatches: async () => {
set({ loading: true, error: null })
try {
const resData = await getTennisMatches() || {};
const { data = {}, code } = resData;
const { count, rows } = data;
set({
matches: rows,
loading: false,
})
} catch (error) {
}
},
// 获取历史搜索数据 // 获取历史搜索数据
getSearchHistory: async () => { getSearchHistory: async () => {
try { try {
@@ -212,10 +229,10 @@ export const useListStore = create<TennisStore>()((set, get) => ({
// 更新综合筛选项 // 更新综合筛选项
updateFilterOptions: (payload: Record<string, any>) => { updateFilterOptions: (payload: Record<string, any>) => {
console.log('===更新综合筛选项', payload)
const preFilterOptions = get()?.filterOptions || {} const preFilterOptions = get()?.filterOptions || {}
const filterOptions = { ...preFilterOptions, ...payload } const filterOptions = { ...preFilterOptions, ...payload }
const filterCount = Object.values(filterOptions).filter(Boolean).length const filterCount = Object.values(filterOptions).filter(Boolean).length
console.log('===更新综合筛选项', filterOptions, filterCount)
set({ set({
filterOptions, filterOptions,
filterCount filterCount

View File

@@ -0,0 +1,255 @@
import { create } from 'zustand'
import { getTennisMatches, getSearchHistory, clearHistory, searchSuggestion } from '../services/listApi'
import { ListActions, ISearchOptions, ListState } from '../../types/list/types'
// 完整的 Store 类型
type TennisStore = ListState & ListActions
const defaultSearchOptions: ISearchOptions = {
title: '', // 标题
dateRange: ['', ''], // 日期区间
timeSlot: '', // 时间段
ntrpMin: 1, // NTRP 最小值
ntrpMax: 5, // NTRP 最大值
venueType: '', // 场地类型
playType: '', // 玩法
distanceFilter: '', // 距离筛选
lat: '', // 纬度
lng: '', // 经度
};
const defaultDistance = 'all'; // 默认距离
// 创建 store
export const useSearchResultStore = create<TennisStore>()((set, get) => ({
// 初始状态
matches: [],
// 推荐列表
recommendList: [],
// 是否加载中
loading: false,
error: null,
// 搜索的value
searchValue: '',
// 是否展示综合筛选弹窗
isShowFilterPopup: false,
// 综合筛选项
searchOption: defaultSearchOptions,
// 综合筛选 选择的筛选数量
filterCount: 0,
// 距离筛选
distance: defaultDistance,
// 快捷筛选
quickFilter: 1, // 1: 默认 2: 好评 3: 销量
// 距离筛选数据
distanceData: [
{ id: 0, label: "全城", value: "全城" },
{ id: 1, label: "3km", value: "3km" },
{ id: 2, label: "5km", value: "5km" },
{ id: 3, label: "10km", value: "10km" },
],
// 快捷筛选数据
quickFilterData: [
{ text: "默认排序", value: "0" },
{ text: "好评排序", value: "1" },
{ text: "销量排序", value: "2" },
],
// 距离筛选和快捷筛选
distanceQuickFilter: {
distance: '全城',
quick: '0',
},
// 时间气泡数据
timeBubbleData: [
{ id: 1, label: "晨间 6:00-10:00", value: "1" },
{ id: 2, label: "上午 10:00-12:00", value: "2" },
{ id: 3, label: "中午 12:00-14:00", value: "3" },
{ id: 4, label: "下午 14:00-18:00", value: "4" },
{ id: 5, label: "晚上 18:00-22:00", value: "5" },
{ id: 6, label: "夜间 22:00-24:00", value: "6" },
],
// 场地类型数据
locationOptions: [
{ id: 1, label: "室内", value: "1" },
{ id: 2, label: "室外", value: "2" },
{ id: 3, label: "半室外", value: "3" },
],
// 玩法数据
gamePlayOptions: [
{ id: 1, label: "不限", value: "不限" },
{ id: 2, label: "单打", value: "单打" },
{ id: 3, label: "双打", value: "双打" },
{ id: 4, label: "娱乐", value: "娱乐" },
{ id: 5, label: "拉球", value: "拉球" },
],
// 球局数量
gamesNum: 124,
// 页面滚动距离顶部距离 是否大于0
isScrollTop: false,
// 搜索历史数据
searchHistory: ['上海', '黄浦', '上海', '静安', '徐汇', '黄浦', '普陀', '黄浦', '长宁', '黄浦'],
// 搜索历史数据默认 Top 15
searchHistoryParams: {
page: 1,
pageSize: 15,
},
// 联想词
suggestionList: [],
// 是否显示联想词
isShowSuggestion: false,
// 列表页是否显示搜索框自定义导航
isShowInputCustomerNavBar: false,
// 结果页是否显示搜索框自定义导航
isShowResultInputCustomerNavBar: false,
// 打开距离筛选框
isOpenDistancePopup: false,
// 打开快捷筛选框
isOpenQuickFilterPopup: false,
// 分页
pageOption: {
page: 1,
pageSize: 10,
},
// 获取比赛数据
fetchMatches: async (params) => {
set({ loading: true, error: null })
try {
const resData = await getTennisMatches(params) || {};
const { data = {}, code } = resData;
if (code !== 0) {
set({
error: '1',
matches: [],
loading: false,
})
}
const { count, rows } = data;
const list = (rows || []).map(() => {
return {
id: "3",
title: "黄浦区双打约球",
dateTime: "7月20日(周日)下午6点 2小时",
location: "仁恒河滨花园网球场",
distance: "3.5km",
registeredCount: 3,
maxCount: 4,
skillLevel: "2.0 至 2.5",
matchType: "双打",
images: [
"https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center",
],
}
})
set({
matches: list || rows || [],
recommendList: list || rows || [],
error: null,
loading: false,
gamesNum: count,
})
} catch (error) {
set({
error,
matches: [],
loading: false,
})
}
},
// 刷新比赛数据
refreshMatches: async () => {
set({ loading: true, error: null })
try {
const resData = await getTennisMatches() || {};
const { data = {}, code } = resData;
const { count, rows } = data;
set({
matches: rows,
loading: false,
})
} catch (error) {
}
},
// 获取历史搜索数据
getSearchHistory: async () => {
try {
const params = get()?.searchHistoryParams || {}
const resData = await getSearchHistory(params) || {};
console.log('===resData', resData)
} catch (error) {
}
},
// 清空历史记录
clearHistory: async () => {
try {
const resData = await clearHistory() || {};
} catch (error) {
}
set({
searchHistory: [],
})
},
// 获取联想
searchSuggestion: async (val: string) => {
try {
const resData = await searchSuggestion({ val }) || {};
console.log('===获取联想', resData)
// set({
// suggestionList: ['上海球局', '黄浦球局', '上海球局', '静安球局', '徐汇球局', '黄浦球局', '普陀球局', '黄浦球局', '长宁球局', '黄浦球局'],
// isShowSuggestion: true,
// })
} catch (error) {
set({
suggestionList: ['上海球局', '黄浦球局', '上海球局', '静安球局', '徐汇球局', '黄浦球局', '普陀球局', '黄浦球局', '长宁球局', '黄浦球局'],
isShowSuggestion: true,
})
}
},
// 清除错误信息
clearError: () => {
set({ error: null })
},
// 更新综合筛选项
updateSearchOptions: (payload: Record<string, any>) => {
console.log('===更新综合筛选项', payload)
const preSearchOptions = get()?.searchOption || {}
const searchOption = { ...preSearchOptions, ...payload }
const filterCount = Object.values(searchOption).filter(Boolean).length
set({
searchOption,
filterCount
})
},
// 清空综合筛选选项
clearSearchOptions: () => {
set({
searchOption: defaultSearchOptions,
filterCount: 0
})
},
// 更新store数据
updateState: (payload: Record<string, any>) => {
const state = get();
console.log('Store: 更新数据:', state);
set({
...(payload || {})
})
}
}))
// 导出便捷的 hooks
export const useSearchResultState = () => useSearchResultStore((state) => state)

View File

@@ -13,15 +13,19 @@ export interface TennisMatch {
shinei: string shinei: string
} }
export interface IFilterOptions { export interface IFilterOptions {
location: string dateRange: [], // 日期区间
time: string timeSlot?: string, // 时间段
ntrp: [number, number] ntrp?: [number, number], // NTRP 水平区间
court_type: string venueType?: string, // 场地类型
game_play: string playType?: string, // 玩法
} }
export interface ListState { export interface ListState {
matches: TennisMatch[] matches: TennisMatch[]
recommendList: TennisMatch[] recommendList: TennisMatch[]
location: {
latitude: number
longitude: number
}
loading: boolean loading: boolean
error: string | null error: string | null
searchValue: string searchValue: string
@@ -49,6 +53,10 @@ export interface ListState {
isShowResultInputCustomerNavBar: boolean isShowResultInputCustomerNavBar: boolean
isOpenDistancePopup: boolean, isOpenDistancePopup: boolean,
isOpenQuickFilterPopup: boolean, isOpenQuickFilterPopup: boolean,
pageOption: {
page: number
pageSize: number
}
} }
export interface ListActions { export interface ListActions {
@@ -58,7 +66,7 @@ export interface ListActions {
location?: string location?: string
skillLevel?: string skillLevel?: string
}) => Promise<void> }) => Promise<void>
refreshMatches: () => Promise<void> // refreshMatches: () => Promise<void>
clearError: () => void clearError: () => void
updateState: (payload: Record<string, any>) => void updateState: (payload: Record<string, any>) => void
updateFilterOptions: (payload: Record<string, any>) => void updateFilterOptions: (payload: Record<string, any>) => void
@@ -66,6 +74,7 @@ export interface ListActions {
getSearchHistory: () => Promise<void> getSearchHistory: () => Promise<void>
clearHistory: () => void clearHistory: () => void
searchSuggestion: (val: string) => Promise<void> searchSuggestion: (val: string) => Promise<void>
getSearchParams: () => Record<string, any>
} }
// 快捷筛选 // 快捷筛选
@@ -94,7 +103,7 @@ export interface DistanceFilterProps {
// bubble 组件 // bubble 组件
export interface BubbleOption { export interface BubbleOption {
id: string | number; id?: string | number;
label: string; label: string;
value: string | number; value: string | number;
disabled?: boolean; disabled?: boolean;