处理列表

This commit is contained in:
李瑞
2025-09-13 17:49:59 +08:00
parent aef84e76cb
commit ba7d904134
15 changed files with 226 additions and 194 deletions

View File

@@ -9,12 +9,12 @@ interface IProps {
const CustomNavbar = (props: IProps) => {
const { children } = props;
const { statusNavbarHeightInfo } = useGlobalState();
const { totalHeight } = statusNavbarHeightInfo;
const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo;
return (
<View
className={styles.customerNavbar}
style={{ height: `${totalHeight}px` }}
style={{ height: `${navbarHeight}px`, paddingTop: `${statusBarHeight}px`, }}
>
{children}
</View>

View File

@@ -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;

View File

@@ -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 (
<View>
<Menu
className={`distanceQuickFilterWrap ${filterWrapperClassName}`}
>
@@ -105,6 +106,7 @@ const Demo3 = (props) => {
</View>
</Menu.Item>
</Menu>
</View>
);
};
export default Demo3;
export default DistanceQuickFilter;

View File

@@ -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}
>

View File

@@ -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;
}
}

View File

@@ -18,7 +18,21 @@ const ListCard: React.FC<ListCardProps> = ({
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 (
<Image
@@ -32,7 +46,6 @@ const ListCard: React.FC<ListCardProps> = ({
};
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<ListCardProps> = ({
<View className="bottom-info">
<View className="left-section">
<View className="avatar-group">
{Array.from({ length: 3 }).map((_, index) => (
{(participantsImageList || []).map((item, index) => (
<View key={index} className="avatar">
<Image
className="avatar-image"
src="https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center"
src={item?.user?.avatar_url}
mode="aspectFill"
/>
</View>
@@ -157,24 +170,24 @@ const ListCard: React.FC<ListCardProps> = ({
<View className="image-section">{renderImages()}</View>
</View>
{/* 畅打球局 */}
<View className="smoothPlayingGame">
{isShowSmoothPlayingGame && <View className="smoothPlayingGame">
<View className="smoothWrapper">
<Image
src={img.ICON_LIST_PLAYING_GAME}
className="iconListPlayingGame"
/>
<Text className="smoothTitle"></Text>
<Text className="smoothTitle">{game_type}</Text>
</View>
<View className="line" />
<View>:</View>
<View className="localAreaTitle">:</View>
<View className="localAreaWrapper">
<Image
className="localArea"
src="https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center"
src={venueImage}
/>
<Text className="localAreaText"></Text>
</View>
<Text className="localAreaText">{venue_description}</Text>
</View>
</View>}
</View>
);
};

View File

@@ -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 <ListCardSkeleton />;
// })}
// </>
// );
// }
// 请求数据为空
if (!loading && list?.length === 0) {
return <ListLoadError reload={reload} text="暂无数据" />;
@@ -58,15 +44,6 @@ const ListContainer = (props) => {
// 渲染数据
return (
<>
{/* <VirtualList
containerHeight={1000}
itemHeight={144}
// itemEqual={false}
list={list}
itemRender={(data) => {
return <ListCard {...data}/>
}}
/> */}
{list?.map((match, index) => (
<ListCard key={match.id || index} {...match} />
))}
@@ -76,6 +53,15 @@ const ListContainer = (props) => {
return (
<View className="listContentWrapper">
{/* <ScrollView
scrollY
scrollWithAnimation
enableBackToTop
enablePassive
style={{ height: '100vh' }}
onScrollToLower={handleScrollToLower}
upperThreshold={60}
> */}
{renderList(data)}
{/* 显示骨架屏 */}
{loading && renderSkeleton()}
@@ -85,6 +71,7 @@ const ListContainer = (props) => {
{renderList(recommendList)} */}
{/* 到底了 */}
{data?.length > 0 && <View className="bottomTextWrapper"></View>}
{/* </ScrollView> */}
</View>
);
};

View File

@@ -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);
}
/* 可见状态 */

View File

@@ -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 (
<CustomNavbar>
<View className="listNavWrapper">
@@ -75,10 +77,7 @@ const ListHeader = (props: IProps) => {
<View
className={`listNavContainer toggleElement firstElement hidden ${(!isShowInputCustomerNavBar && !showInput) && "visible"
}`}
style={{
height: `${navbarHeight}px`,
paddingTop: `${statusBarHeight}px`,
}}
style={navbarStyle}
>
<View className="listNavContentWrapper">
{/* logo */}
@@ -106,10 +105,7 @@ const ListHeader = (props: IProps) => {
<View
className={`inputCustomerNavbarContainer toggleElement secondElement hidden ${(isShowInputCustomerNavBar || showInput) && "visible"
} ${showInput && "inputCustomerNavbarShowInput"}`}
style={{
height: `${navbarHeight}px`,
paddingTop: `${statusBarHeight}px`,
}}
style={navbarStyle}
>
<View className="navContent">
{/* logo */}
@@ -137,4 +133,5 @@ const ListHeader = (props: IProps) => {
</CustomNavbar>
);
};
export default ListHeader;

View File

@@ -18,6 +18,9 @@
padding-top: 10px;
padding-bottom: 10px;
gap: 5px;
position: sticky;
background-color: #fff;
z-index: 100;
}
.menuFilter {

View File

@@ -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 (
<View>
<View ref={scrollContextRef}>
{/* 自定义导航 */}
<CustomerNavBar />
{/* <ShareCardCanvas /> */}
<View className={styles.listPage}>
<View
@@ -187,7 +199,7 @@ const ListPage = () => {
<FilterPopup
loading={loading}
onCancel={toggleShowPopup}
onConfirm={toggleShowPopup}
onConfirm={handleFilterConfirm}
onChange={handleUpdateFilterOptions}
filterOptions={filterOptions}
onClear={clearFilterOptions}
@@ -199,7 +211,10 @@ const ListPage = () => {
)}
</View>
{/* 筛选 */}
<View className={styles.listTopFilterWrapper}>
<View className={styles.listTopFilterWrapper}
style={{
top: totalHeight -1,
}}>
<DistanceQuickFilter
cityOptions={distanceData}
quickOptions={quickFilterData}
@@ -221,7 +236,6 @@ const ListPage = () => {
loadMoreMatches={loadMoreMatches}
/>
</View>
<GuideBar currentPage="list" />
</View>
);

View File

@@ -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})
}
}, []);

View File

@@ -46,6 +46,20 @@ export const getGamesIntegrateList = async (params?: Record<string, any>) => {
}
};
/**
* 获取列表数量
* @param params
* @returns
*/
export const getGamesCount = async (params?: Record<string, any>) => {
try {
return httpService.post('/games/count', params, { showLoading: true })
} catch (error) {
console.error("列表数量获取失败:", error);
throw error;
}
};
/**
* 获取搜索历史记录的异步函数
* @param {Object} params - 查询参数对象

View File

@@ -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<TennisStore>()((set, get) => ({
],
// 球局数量
gamesNum: 0,
// 是否还有更多数据
isHasMoreData: true,
// 页面滚动距离顶部距离 是否大于0
isScrollTop: false,
// 搜索历史数据
@@ -143,22 +144,31 @@ export const useListStore = create<TennisStore>()((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)
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<TennisStore>()((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<TennisStore>()((set, get) => ({
// 更新综合筛选项
updateFilterOptions: (payload: Record<string, any>) => {
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<TennisStore>()((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数据

View File

@@ -45,6 +45,7 @@ export interface ListState {
timeBubbleData: BubbleOption[]
dateRangeOptions: BubbleOption[]
gamesNum: number
isHasMoreData: boolean
isScrollTop: boolean
searchHistoryParams: Record<string, any>
searchHistory: {id: number, title: string}[]
@@ -61,7 +62,7 @@ export interface ListState {
}
export interface ListActions {
fetchMatches: (params?: Record<string, any>,isFirstLoad?: Boolean) => Promise<void>
fetchMatches: (params?: Record<string, any>, isFirstLoad?: Boolean, isAppend?: boolean) => Promise<void>
// getIntegrateListData: (params?: Record<string, any>) => Promise<void>
getMatchesData: () => void
clearError: () => void
@@ -75,13 +76,15 @@ export interface ListActions {
loadMoreMatches: () => void
initialFilterSearch: () => void
setListData: (payload: IPayload) => void
fetchGetGamesCount: () => Promise<void>
}
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;
}