import { create } from "zustand"; import dayjs from "dayjs"; import { getGamesList, getGamesIntegrateList, getSearchHistory, clearHistory, searchSuggestion, getGamesCount, getCities, getCityQrCode, getDistricts, } from "../services/listApi"; // 不再在这里请求 banner 字典,统一由 dictionaryStore 启动时获取 import { useDictionaryStore } from "./dictionaryStore"; import { ListActions, IFilterOptions, ListState, IPayload, } from "../../types/list/types"; // 将 banner 按索引插入到列表的工具方法(0基;长度不足则插末尾;先移除已存在的 banner) function insertBannersToRows(rows: any[], dictData: any) { if (!Array.isArray(rows) || !dictData) return rows; const img = (dictData?.bannerListImage || "").trim(); const indexRaw = (dictData?.bannerListIndex || "").toString().trim(); if (!img) return rows; const parsed = parseInt(indexRaw, 10); const normalized = Number.isFinite(parsed) ? parsed : 0; // 先移除已有的 banner,确保列表中仅一条 banner const resultRows = rows?.filter((item) => item?.type !== "banner") || []; const target = Math.max(0, Math.min(normalized, resultRows.length)); resultRows.splice(target, 0, { type: "banner", id: `banner-${target}`, banner_image_url: img, banner_detail_url: (dictData?.bannerDetailImage || "").trim(), } as any); return resultRows; } function translateCityData(dataTree) { return dataTree.map((item) => { const { children, ...rest } = item; // 只保留两级:国家和省份,去掉第三级(区域) const processedChildren = children?.length > 0 ? children.map(child => ({ ...child, text: child.name, label: child.name, value: child.name, children: null, // 去掉第三级 })) : null; return { ...rest, text: rest.name, label: rest.name, value: rest.name, children: processedChildren, }; }); } // 完整的 Store 类型 type TennisStore = ListState & ListActions; const defaultDateRange: [string, string] = [dayjs().format('YYYY-MM-DD'), dayjs().add(1, 'M').format('YYYY-MM-DD')] const defaultFilterOptions: IFilterOptions = { dateRange: defaultDateRange, // 日期区间 timeSlot: [], // 时间段(多选,默认为空数组) ntrp: [1, 5], // NTRP 水平区间 venueType: [], // 场地类型(多选,默认为空数组) playType: [], // 玩法(多选,默认为空数组) }; // const defaultDistance = "all"; // 默认距离 const defaultDistanceQuickFilter = { distanceFilter: "", order: "0", district: "", // 新增:行政区筛选 }; const defaultPageOption = { page: 1, pageSize: 20, }; // 页面状态默认值 const pageStateDefaultValue = { // 列表数据 data: [], // 推荐列表 recommendList: [], // 是否展示综合筛选弹窗 isShowFilterPopup: false, // 综合筛选项 filterOptions: defaultFilterOptions, // 距离筛选和快捷筛选 distanceQuickFilter: defaultDistanceQuickFilter, // 综合筛选 选择的筛选数量 filterCount: 0, // 分页 pageOption: defaultPageOption, // 球局数量 gamesNum: 0, // 是否还有更多数据 isHasMoreData: true, // 是否展示无数据 isShowNoData: false, } // 列表页状态 const listPageStateDefaultValue = { ...pageStateDefaultValue, // 列表页是否显示搜索框自定义导航 isShowInputCustomerNavBar: false, } // 搜索页状态 const searchPageStateDefaultValue = { ...pageStateDefaultValue, // 搜索结果数据 data: [], // 联想词 suggestionList: [], // 是否显示联想词 isShowSuggestion: false, // 搜索历史数据 searchHistory: [], // 搜索历史数据默认 Top 15 searchHistoryParams: { page: 1, pageSize: 15, }, } // const now = new Date(); // 公共属性 const commonStateDefaultValue = { // 是否是搜索结果页 isSearchResult: false, // 是否加载中 loading: false, // 错误信息 error: null, // 位置 location: { latitude: 0, longitude: 0, }, // 搜索的value searchValue: "", // 日期区间 // dateRange: [now, new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)], // 距离筛选数据 distanceData: [ { id: 0, label: "全城", value: "" }, { id: 1, label: "3km", value: "3" }, { id: 2, label: "5km", value: "5" }, { id: 3, label: "10km", value: "10" }, ], // 快捷筛选数据 quickFilterData: [ { label: "智能排序", value: "0" }, { label: "距离更近", value: "distance" }, { label: "时间更近", value: "time" }, ], // 气泡日期范围 dateRangeOptions: [ { id: 1, label: "本周末", value: "1" }, { id: 2, label: "一周内", value: "2" }, { id: 3, label: "一月内", value: "3" }, ], // 时间气泡数据 timeBubbleData: [ { id: 1, label: "晨间 6:00-10:00", value: "6:00-10:00" }, { id: 2, label: "上午 10:00-12:00", value: "10:00-12:00" }, { id: 3, label: "中午 12:00-14:00", value: "12:00-14:00" }, { id: 4, label: "下午 14:00-18:00", value: "14:00-18:00" }, { id: 5, label: "晚上 18:00-22:00", value: "18:00-22:00" }, { id: 6, label: "夜间 22:00-24:00", value: "22:00-24:00" }, ], cities: [], cityQrCode: [], area: ['', ''] as [string, string], // 改为两级:国家、省份 districts: [], // 新增:行政区列表 } // 创建 store export const useListStore = create()((set, get) => ({ currentPage: "", // 列表页 listPageState: listPageStateDefaultValue, // 搜索及搜索结果页 searchPageState: searchPageStateDefaultValue, ...commonStateDefaultValue, gamesNum: 0, // 组装搜索数据 // 注意:始终使用 state.area,不接收 overrideArea 参数,确保参数一致性 getSearchParams: () => { const state = get(); const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState; const filterOptions = currentPageState?.filterOptions || {}; // 全城和快捷筛选 const distanceQuickFilter = currentPageState?.distanceQuickFilter || {}; const { distanceFilter, order, district } = distanceQuickFilter || {}; // 始终使用 state.area,确保所有接口使用一致的城市参数 const areaProvince = state.area?.at(1) || ""; const last_location_province = areaProvince; // city 参数逻辑: // 1. 如果选择了行政区(district 有值),使用行政区的名称(label) // 2. 如果是"全城"(distanceFilter 为空),不传 city let city: string | undefined = undefined; if (district) { // 从 districts 数组中查找对应的行政区名称 const selectedDistrict = state.districts.find(item => item.value === district); if (selectedDistrict) { city = selectedDistrict.label; // 传递行政区名称,如"静安" } } // 如果是"全城"(distanceFilter 为空),city 保持 undefined,不会被传递 // 使用 filterOptions 中的 dateRange const dateRange: [string, string] = filterOptions?.dateRange || defaultDateRange; // 构建 searchOption // 注意:province 必须从 state.area 获取,不能依赖 filterOptions 中可能存在的旧值 const searchOption: any = { ...filterOptions, title: state.searchValue, ntrpMin: filterOptions?.ntrp?.[0], ntrpMax: filterOptions?.ntrp?.[1], dateRange: dateRange, // 确保始终是两个值的数组 distanceFilter: distanceFilter, // 显式设置 province,确保始终使用 state.area 中的最新值 province: last_location_province, // 始终使用 state.area 中的 province,确保城市参数一致 }; // 只在有值时添加 city 参数 if (city) { searchOption.city = city; } const params = { pageOption: currentPageState?.pageOption, seachOption: searchOption, order: order, lat: state?.location?.latitude, lng: state?.location?.longitude, }; return params; }, // 设置列表结果 setListData: (payload: IPayload & { isAppend?: boolean }) => { const state = get(); const { error, data, loading, count, isAppend = false } = payload; const isHasMoreData = count > 0; const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState; const currentData = currentPageState?.data || []; const newData = isAppend ? [...currentData, ...(data || [])] : (data || []); // 从字典缓存获取 banner,并将其插入到最终列表指定位置(全局索引) const dictData = useDictionaryStore.getState().bannerDict; const processedData = dictData ? insertBannersToRows(newData, dictData) : newData; state.updateCurrentPageState({ data: processedData, isHasMoreData, // 使用插入后的最终数据判断是否显示空状态,避免有 banner 时仍显示空 isShowNoData: processedData?.length === 0, }); set({ error, loading, }); }, // 获取列表数据(常规搜索) fetchMatches: async (params, isFirstLoad = false, isAppend = false) => { if (get().loading) { return; } set({ loading: true, error: null }); const { getSearchParams, setListData } = get(); try { const searchParams = getSearchParams() || {}; const reqParams = { ...(searchParams || {}), ...params, }; // 获取当前页面的距离筛选 const state = get(); const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState; const distanceQuickFilter = currentPageState?.distanceQuickFilter || {}; // 是否选择了智能排序 const isIntegrate = distanceQuickFilter?.order === "0"; let fetchFn = getGamesList; if (isIntegrate) { reqParams.order = ""; fetchFn = getGamesIntegrateList; // 第一次进入页面时传入 isRefresh 参数 if (isFirstLoad) { reqParams.seachOption.isRefresh = true; } } let resData: any = {}; resData = (await fetchFn(reqParams)) || {}; const { data = {}, code } = resData; if (code !== 0) { setListData({ error: "-1", data: [], loading: false, count: 0, isAppend, }); return Promise.reject(new Error('获取数据失败')); } const { count } = data; let { rows } = data as any; setListData({ error: '', data: rows || [], loading: false, count, isAppend, }); return Promise.resolve(); } catch (error) { setListData({ error: "", data: [], loading: false, count: 0, isAppend, }); return Promise.reject(error); } }, // 获取列表数据 getMatchesData: async () => { const { fetchMatches } = get(); return await fetchMatches({}, true); // 第一次进入页面,传入 isFirstLoad = true }, // 同时更新两个列表接口(常规列表和智能排序列表) // 注意:不再接收 overrideArea 参数,始终使用 state.area refreshBothLists: async () => { const state = get(); const { getSearchParams, setListData } = state; const { getGamesList, getGamesIntegrateList } = await import("../services/listApi"); try { const searchParams = getSearchParams() || {}; // 并发请求:常规列表、智能排序列表 const [listResSettled, integrateResSettled] = await Promise.allSettled([ getGamesList({ ...searchParams, order: searchParams.order || "distance", }), getGamesIntegrateList({ ...searchParams, order: "", seachOption: { ...searchParams.seachOption, isRefresh: true, }, }), ]); const listRes = listResSettled.status === "fulfilled" ? listResSettled.value : null; const integrateRes = integrateResSettled.status === "fulfilled" ? integrateResSettled.value : null; // 根据当前排序方式更新对应的数据 const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState; const distanceQuickFilter = currentPageState?.distanceQuickFilter || {}; const isIntegrate = distanceQuickFilter?.order === "0"; if (listRes?.code === 0 && listRes?.data) { const { count } = listRes.data; let { rows } = listRes.data as any; if (!isIntegrate) { // 如果当前是常规排序,更新常规列表数据 setListData({ error: '', data: rows || [], loading: false, count, isAppend: false, }); } } if (integrateRes?.code === 0 && integrateRes?.data) { const { count } = integrateRes.data; let { rows, recommendList } = integrateRes.data as any; if (isIntegrate) { // 如果当前是智能排序,更新智能排序列表数据 setListData({ error: '', data: rows || [], loading: false, count, isAppend: false, }); } // 无论当前排序方式如何,都更新推荐列表 state.updateCurrentPageState({ recommendList: recommendList || [], }); } return Promise.resolve(); } catch (error) { console.error("更新列表数据失败:", error); return Promise.reject(error); } }, // 获取球局数量 // 注意:必须和 games/integrate_list 使用相同的参数构建逻辑,确保数据一致性 // 不再接收 overrideArea 参数,始终使用 state.area fetchGetGamesCount: async () => { const state = get(); const { getSearchParams } = state; const searchParams = getSearchParams() || {}; // 使用和 games/integrate_list 相同的参数构建逻辑 const params = { ...searchParams, order: "", // 和 integrate_list 保持一致 seachOption: { ...searchParams.seachOption, isRefresh: true, // 和 integrate_list 保持一致 }, }; console.log("fetchGetGamesCount 参数:", { area: state.area, params: JSON.stringify(params) }); const resData = (await getGamesCount(params)) || {}; const gamesNum = resData?.data?.count || 0; console.log("fetchGetGamesCount 结果:", gamesNum); set({ gamesNum }); }, // 获取历史搜索数据 getSearchHistory: async () => { try { const state = get(); const params = state.searchPageState?.searchHistoryParams || {}; const resData = (await getSearchHistory(params)) || {}; const searchHistory = resData?.data?.records || []; set({ searchPageState: { ...state.searchPageState, searchHistory, }, }); } catch (error) { } }, // 清空历史记录 clearHistory: async () => { try { const state = get(); const params = {}; const resData = (await clearHistory(params)) || {}; if (resData?.code === 0) { set({ searchPageState: { ...state.searchPageState, searchHistory: [], }, }); } } catch (error) { } }, // 获取联想 searchSuggestion: async (val: string) => { try { const state = get(); const resData = (await searchSuggestion({ keyword: val, limit: 10 })) || {}; const recommendations = resData?.data?.recommendations || []; const total = resData?.data?.total; set({ searchPageState: { ...state.searchPageState, suggestionList: recommendations, isShowSuggestion: total > 0, }, }); } catch (error) { const state = get(); set({ searchPageState: { ...state.searchPageState, suggestionList: [], isShowSuggestion: true, }, }); } }, // 清除错误信息 clearError: () => { set({ error: null }); }, getCurrentPageState: () => { const state = get(); return { currentPageState: state.isSearchResult ? state.searchPageState : state.listPageState, currentPageKey: state.isSearchResult ? "searchPageState" : "listPageState", }; }, // 更新当前页面状态 updateCurrentPageState: (payload: Record) => { const state = get(); const { currentPageState, currentPageKey } = state.getCurrentPageState(); set({ [currentPageKey]: { ...currentPageState, ...payload } }); }, // 更新综合筛选项 updateFilterOptions: (payload: Record) => { const state = get(); const { currentPageState } = state.getCurrentPageState(); const filterOptions = { ...currentPageState?.filterOptions, ...payload }; // 计算筛选数量:排除 dateRange、ntrp 默认值,以及空数组和空字符串 const filterCount = Object.entries(filterOptions).filter(([key, value]) => { if (key === 'dateRange') return false; // 日期区间不算筛选 if (key === 'ntrp') { // NTRP 只有不是默认值 [1, 5] 时才算筛选 const ntrp = value as [number, number]; return ntrp && (ntrp[0] !== 1 || ntrp[1] !== 5); } // 数组为空数组或字符串为空字符串时不算筛选 if (Array.isArray(value)) return value.length > 0; if (typeof value === 'string') return value !== ''; return Boolean(value); }).length; // 先更新状态 state.updateCurrentPageState({ filterOptions, filterCount, pageOption: defaultPageOption, }); // 使用 Promise.resolve 确保状态更新后再调用接口 // 先调用列表接口,然后在列表接口完成后调用数量接口 Promise.resolve().then(async () => { const freshState = get(); // 重新获取最新状态 // 先调用列表接口 await freshState.getMatchesData(); // 列表接口完成后,再调用数量接口 await freshState.fetchGetGamesCount(); }); }, // 更新距离和快捷筛选 updateDistanceQuickFilter: (payload: Record) => { const state = get(); const { currentPageState } = state.getCurrentPageState(); const { distanceQuickFilter } = currentPageState || {}; const newDistanceQuickFilter = { ...distanceQuickFilter, ...payload }; // 先更新状态 state.updateCurrentPageState({ distanceQuickFilter: newDistanceQuickFilter, pageOption: defaultPageOption, }); // 使用 Promise.resolve 确保状态更新后再调用接口 // 先调用列表接口,然后在列表接口完成后调用数量接口 Promise.resolve().then(async () => { const freshState = get(); // 重新获取最新状态 // 先调用列表接口 await freshState.getMatchesData(); // 列表接口完成后,再调用数量接口 await freshState.fetchGetGamesCount(); }); }, // 清空综合筛选选项 clearFilterOptions: async () => { const state = get(); const { getMatchesData, fetchGetGamesCount } = state; state.updateCurrentPageState({ filterOptions: defaultFilterOptions, filterCount: 0, pageOption: defaultPageOption, }); // 先调用列表接口 await getMatchesData(); // 列表接口完成后,再调用数量接口 await fetchGetGamesCount(); }, // 加载更多数据 loadMoreMatches: async () => { const state = get(); const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState; const { pageOption, isHasMoreData } = currentPageState || {}; if (!isHasMoreData) { return Promise.resolve(); } const newPageOption = { page: (pageOption?.page || 1) + 1, pageSize: 20, }; state.updateCurrentPageState({ pageOption: newPageOption, }); // 加载更多时追加数据到现有数组 return await state.fetchMatches({}, false, true); }, // 初始化搜索条件 重新搜索 initialFilterSearch: async (isSearchData = false) => { const state = get(); const { getMatchesData, fetchGetGamesCount } = state; if (state.isSearchResult) { set({ searchPageState: { ...searchPageStateDefaultValue }, // loading: true, }); } else { set({ listPageState: { ...listPageStateDefaultValue }, // loading: true, }); } if (!isSearchData) { return; } // 先调用列表接口 await getMatchesData(); // 列表接口完成后,再调用数量接口 await fetchGetGamesCount(); }, // 更新store数据 updateState: (payload: Record) => { set({ ...(payload || {}), }); }, // 更新列表页状态中的特定字段 updateListPageState: (payload: Record) => { console.log("===更新列表页状态:", payload); const state = get(); set({ listPageState: { ...state.listPageState, ...payload, }, }); }, // 更新搜索页状态中的特定字段 updateSearchPageState: (payload: Record) => { const state = get(); set({ searchPageState: { ...state.searchPageState, ...payload, }, }); console.log("===更新搜索页状态:", state); }, async getCities() { const res = await getCities(); const state = get(); set({ ...state, cities: translateCityData(res.data), }) }, async getCityQrCode() { const res = await getCityQrCode(); const state = get(); set({ ...state, cityQrCode: res.data, }) }, // 新增:获取行政区列表 async getDistricts() { try { const state = get(); // 从 area 中获取省份,area 格式: ["中国", 省份, 城市] const country = "中国"; const province = state.area?.at(1) || "上海"; // area[1] 是省份 const res = await getDistricts({ country, state: province }); if (res.code === 0 && res.data) { const districts = res.data.map((item) => ({ label: item.cn_city, value: item.id.toString(), id: item.id, })); set({ districts }); return districts; } return []; } catch (error) { console.error("获取行政区列表失败:", error); return []; } }, updateArea(payload: [string, string]) { set({ area: payload, }) }, })); // 导出便捷的 hooks export const useListState = () => useListStore((state) => state);