This commit is contained in:
juguohong
2025-08-30 18:20:50 +08:00
parent 1cb303b86d
commit d92419f3c5
23 changed files with 456 additions and 266 deletions

View File

@@ -2,6 +2,9 @@ import { Component, ReactNode } from 'react'
import './nutui-theme.scss'
import './app.scss'
import { useDictionaryStore } from './store/dictionaryStore'
import { useGlobalStore } from './store/global'
// import { getNavbarHeight } from "@/utils/getNavbarHeight";
interface AppProps {
children: ReactNode
@@ -11,11 +14,12 @@ class App extends Component<AppProps> {
componentDidMount() {
// 初始化字典数据
this.initDictionaryData()
this.getNavBarHeight()
}
componentDidShow() {}
componentDidShow() { }
componentDidHide() {}
componentDidHide() { }
// 初始化字典数据
private async initDictionaryData() {
@@ -27,6 +31,13 @@ class App extends Component<AppProps> {
}
}
// 获取导航高度
getNavBarHeight = () => {
const { getNavbarHeightInfo } = useGlobalStore.getState()
getNavbarHeightInfo()
}
render() {
// this.props.children 是将要会渲染的页面
return this.props.children

View File

@@ -1,5 +1,8 @@
.customerNavbar {
// background-color: red;
position: sticky;
top: 0;
z-index: 999;
background-color: #ffffff;
.container {
padding-left: 17px;

View File

@@ -1,22 +1,22 @@
import { View, Text, Image } from "@tarojs/components";
import img from "@/config/images";
import { getCurrentLocation } from "@/utils/locationUtils";
import { getNavbarHeight } from "@/utils/getNavbarHeight";
import styles from "./index.module.scss";
import { useEffect } from "react";
import { useGlobalState } from "@/store/global";
import { useListState } from "@/store/listStore";
const ListHeader = () => {
const { statusBarHeight, navbarHeight, totalHeight } = getNavbarHeight();
const {
updateState,
location,
getLocationText,
getLocationLoading,
getNavbarHeightInfo,
statusNavbarHeightInfo,
} = useGlobalState();
const { gamesNum } = useListState();
console.log("===statusNavbarHeightInfo", statusNavbarHeightInfo);
const { statusBarHeight, navbarHeight, totalHeight } = statusNavbarHeightInfo;
// 获取位置信息
const getCurrentLocal = () => {
@@ -31,7 +31,7 @@ const ListHeader = () => {
});
};
useEffect(() => {
getNavbarHeightInfo();
// getNavbarHeightInfo();
getCurrentLocal();
}, []);

View File

@@ -14,6 +14,7 @@
justify-content: space-between;
align-items: center;
padding: 20px 12px env(safe-area-inset-bottom);
z-index: 999;
&-pages {
display: flex;

View File

@@ -1,4 +1,10 @@
.list-item {
.listCard {
background: linear-gradient(90deg, rgba(183, 248, 113, 0.5) 0%, rgba(183, 248, 113, 0.1) 100%);
border-radius: 20px;
border-width: 0.5px;
}
.listItem {
display: flex;
padding: 12px 15px;
background: #ffffff;
@@ -247,4 +253,43 @@
width: 100%;
height: 100%;
object-fit: cover;
}
// 底部
.smoothPlayingGame {
padding: 5px 12px;
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
.smoothWrapper,
.localAreaWrapper {
line-height: 18px;
display: flex;
align-items: center;
gap: 5px;
}
.smoothTitle {
font-size: 14px;
}
.line {
height: 8px;
width: 1px;
background: #00000040;
border-radius: 99px;
}
.iconListPlayingGame,
.localArea {
width: 14px;
height: 14px;
}
.localArea {
border: 0.5px solid #FFFFFFA6;
border-radius: 50%;
}
}

View File

@@ -1,5 +1,5 @@
import { View, Text, Image } from "@tarojs/components";
import Taro from '@tarojs/taro'
import Taro from "@tarojs/taro";
import img from "../../config/images";
import { ListCardProps } from "../../../types/list/types";
import "./index.scss";
@@ -14,7 +14,7 @@ const ListCard: React.FC<ListCardProps> = ({
maxCount,
skillLevel,
matchType,
images=[],
images = [],
shinei,
}) => {
const renderItemImage = (src: string) => {
@@ -23,9 +23,9 @@ const ListCard: React.FC<ListCardProps> = ({
const handleViewDetail = () => {
Taro.navigateTo({
url: `/pages/detail/index?id=${id || 1}&from=list&autoShare=0`
})
}
url: `/pages/detail/index?id=${id || 1}&from=list&autoShare=0`,
});
};
// 根据图片数量决定展示样式
const renderImages = () => {
@@ -34,9 +34,7 @@ const ListCard: React.FC<ListCardProps> = ({
if (images?.length === 1) {
return (
<View className="single-image">
<View className="image-container">
{renderItemImage(images[0])}
</View>
<View className="image-container">{renderItemImage(images[0])}</View>
</View>
);
}
@@ -44,12 +42,8 @@ const ListCard: React.FC<ListCardProps> = ({
if (images?.length === 2) {
return (
<View className="double-image">
<View className="image-container">
{renderItemImage(images[0])}
</View>
<View className="image-container">
{renderItemImage(images[1])}
</View>
<View className="image-container">{renderItemImage(images[0])}</View>
<View className="image-container">{renderItemImage(images[1])}</View>
</View>
);
}
@@ -64,72 +58,87 @@ const ListCard: React.FC<ListCardProps> = ({
);
};
return (
<View className="list-item" onClick={handleViewDetail}>
{/* 左侧内容区域 */}
<View className="content">
{/* 标题 */}
<View className="titleWrapper">
<Text className="title">{title}</Text>
<Image
src={img.ICON_LIST_RIGHT_ARROW}
className="title-right-arrow"
mode="aspectFit"
/>
</View>
{/* 时间信息 */}
<View className="date-time">
<Text>{dateTime}</Text>
</View>
{/* 地点,室内外,距离 */}
<View className="location">
<Text className="location-text location-position">{location}</Text>
<Text className="location-text location-time-distance">
{shinei && `${shinei}`}
{distance && `${distance}`}
</Text>
</View>
{/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */}
<View className="bottom-info">
<View className="left-section">
<View className="avatar-group">
{Array.from({ length: Math.min(registeredCount, 3) }).map(
(_, 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"
mode="aspectFill"
/>
</View>
)
)}
</View>
<View className="listCard">
<View className="listItem" onClick={handleViewDetail}>
{/* 左侧内容区域 */}
<View className="content">
{/* 标题 */}
<View className="titleWrapper">
<Text className="title">{title}</Text>
<Image
src={img.ICON_LIST_RIGHT_ARROW}
className="title-right-arrow"
mode="aspectFit"
/>
</View>
<View className="tags">
<View className="tag">
<Text className="tag-text">
{registeredCount}/
<Text className="tag-text-max">{maxCount}</Text>
</Text>
{/* 时间信息 */}
<View className="date-time">
<Text>{dateTime}</Text>
</View>
{/* 地点,室内外,距离 */}
<View className="location">
<Text className="location-text location-position">{location}</Text>
<Text className="location-text location-time-distance">
{shinei && `${shinei}`}
{distance && `${distance}`}
</Text>
</View>
{/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */}
<View className="bottom-info">
<View className="left-section">
<View className="avatar-group">
{Array.from({ length: Math.min(registeredCount, 3) }).map(
(_, 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"
mode="aspectFill"
/>
</View>
)
)}
</View>
</View>
<View className="tag">
<Text className="tag-text">{skillLevel}</Text>
</View>
<View className="tag">
<Text className="tag-text">{matchType}</Text>
<View className="tags">
<View className="tag">
<Text className="tag-text">
{registeredCount}/
<Text className="tag-text-max">{maxCount}</Text>
</Text>
</View>
<View className="tag">
<Text className="tag-text">{skillLevel}</Text>
</View>
<View className="tag">
<Text className="tag-text">{matchType}</Text>
</View>
</View>
</View>
</View>
{/* 右侧图片区域 */}
<View className="image-section">{renderImages()}</View>
</View>
{/* 畅打球局 */}
<View className="smoothPlayingGame">
<View className="smoothWrapper">
<Image src={img.ICON_LIST_PLAYING_GAME} className="iconListPlayingGame" />
<Text className="smoothTitle"></Text>
</View>
<View className="line" />
<View>:</View>
<View className="localAreaWrapper">
<Image className="localArea" src="https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center" />
<Text className="localAreaText"></Text>
</View>
</View>
{/* 右侧图片区域 */}
<View className="image-section">{renderImages()}</View>
</View>
);
};

View File

@@ -13,7 +13,6 @@ const ListCard = () => {
</View>
{/* 时间信息 */}
<View className="date-time">
<Skeleton visible={false} style={{ width: "88px", }} />
</View>

View File

@@ -0,0 +1,44 @@
.listLoadError {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
.listLoadErrorImg {
width: 154px;
height: 154px;
}
.listLoadErrorText {
margin-top: 35px;
margin-bottom: 12px;
font-weight: 500;
font-style: Medium;
font-size: 14px;
line-height: 24px;
letter-spacing: 0px;
}
.listLoadErrorBtn {
display: flex;
align-items: center;
justify-content: center;
width: 76px;
background: #00000008;
border: 0.5px solid #0000001F;
border-radius: 12px;
padding: 12px 0;
font-weight: 500;
font-style: Medium;
font-size: 14px;
line-height: 24px;
letter-spacing: 0px;
}
.reloadIcon {
width: 16px;
height: 16px;
}
}

View File

@@ -0,0 +1,24 @@
import { Image, View, Text, Button } from "@tarojs/components";
import styles from "./index.module.scss";
import img from "@/config/images";
const ListLoadError = ({ reload }: { reload: () => void }) => {
const handleReload = () => {
reload && typeof reload === "function" && reload();
};
return (
<View className={styles.listLoadError}>
<Image
className={styles.listLoadErrorImg}
src={img.ICON_LIST_LOAD_ERROR}
/>
<Text className={styles.listLoadErrorText}></Text>
<Button className={styles.listLoadErrorBtn} onClick={handleReload}>
<Image src={img?.ICON_LIST_RELOAD} className={styles.reloadIcon} />
</Button>
</View>
);
};
export default ListLoadError;

View File

@@ -5,9 +5,16 @@
--nutui-searchbar-input-text-color: #000000;
--nutui-searchbar-input-padding: 0 0 0 10px;
--nutui-searchbar-padding: 10px 0 0 0;
:global(.nut-searchbar-content) {
box-shadow: 0 4px 48px #00000014;
}
.searchBarLeft {
display: flex;
align-items: center;
}
.searchBarRight {
position: relative;
width: 44px;
@@ -18,14 +25,17 @@
display: flex;
align-items: center;
justify-content: center;
&.active {
background-color: #000000;
}
}
.filterIcon {
width: 20px;
height: 20px;
}
.filterCount {
background-color: #000000;
position: absolute;
@@ -41,8 +51,9 @@
justify-content: center;
font-size: 11px;
}
.searchIcon {
width: 20px;
height: 20px;
}
}
}

View File

@@ -12,6 +12,7 @@ interface IProps {
const SearchBarComponent = (props: IProps) => {
const { handleFilterIcon, isSelect, filterCount, onChange } = props;
const handleChange = (value: string) => {
onChange && onChange(value);
};
@@ -19,9 +20,9 @@ const SearchBarComponent = (props: IProps) => {
<>
<SearchBar
leftIn={
<div>
<View className={styles.searchBarLeft}>
<Image className={styles.searchIcon} src={img.ICON_SEARCH} />
</div>
</View>
}
right={
<View

View File

@@ -43,4 +43,7 @@ export default {
ICON_DETAIL_SHARE: require('@/static/detail/icon-share-dark.svg'),
ICON_GUIDE_BAR_PUBLISH: require('@/static/common/guide-bar-publish.svg'),
ICON_NAVIGATOR_BACK: require('@/static/common/navigator-back.svg'),
ICON_LIST_PLAYING_GAME: require('@/static/list/icon-paying-game.svg'),
ICON_LIST_LOAD_ERROR: require('@/static/list/icon-load-error.svg'),
ICON_LIST_RELOAD: require('@/static/list/icon-reload.svg'),
}

View File

@@ -0,0 +1,8 @@
.listContentWrapper {
padding: 0 5px;
background: #fafafa;
display: flex;
flex-direction: column;
gap: 5px;
background-color: red;
}

View File

@@ -0,0 +1,52 @@
import { View } from "@tarojs/components";
import ListCard from "@/components/ListCard";
import ListLoadError from "@/components/ListLoadError";
import ListCardSkeleton from "@/components/ListCardSkeleton";
// import { useGlobalState } from "@/store/global";
const ListContainer = (props) => {
const { loading, data = [], error, reload } = props;
// const { statusNavbarHeightInfo } = useGlobalState() || {};
// const { totalHeight } = statusNavbarHeightInfo;
const renderList = () => {
if (loading && data.length > 0) {
return (
<>
{new Array(10).fill(0).map(() => {
return <ListCardSkeleton />;
})}
</>
);
}
if (error) {
return <ListLoadError reload={reload} />;
}
if (loading && data.length === 0) {
return null;
}
return (
<>
{data.map((match, index) => (
<ListCard key={match.id || index} {...match} />
))}
</>
);
};
return (
<View
className="listContentWrapper"
// style={{
// minHeight: `calc(100vh - ${totalHeight}px)`,
// }}
>
{renderList()}
</View>
);
};
export default ListContainer;

View File

@@ -12,6 +12,8 @@ import { FilterPopupProps } from "../../../types/list/types";
import CourtType from "@/components/CourtType";
// 玩法
import GamePlayType from "@/components/GamePlayType";
import { useDictionaryActions } from "@/store/dictionaryStore";
import { useMemo } from "react";
const FilterPopup = (props: FilterPopupProps) => {
const {
@@ -27,7 +29,22 @@ const FilterPopup = (props: FilterPopupProps) => {
} = props;
const store = useListStore() || {};
const { timeBubbleData, locationOptions, gamePlayOptions } = store;
const { getDictionaryValue } = useDictionaryActions() || {};
const { timeBubbleData } = store;
const handleOptions = (dictionaryValue: []) => {
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
};
const courtType = getDictionaryValue("court_type") || [];
const locationOptions = useMemo(() => {
return courtType ? handleOptions(courtType) : [];
}, [courtType]);
const gamePlay = getDictionaryValue("game_play") || [];
const gamePlayOptions = useMemo(() => {
return gamePlay ? handleOptions(gamePlay) : [];
}, [gamePlay]);
const handleFilterChange = (name, value) => {
onChange({ [name]: value });

View File

@@ -3,20 +3,23 @@
.listTopSearchWrapper {
padding: 0 15px;
position: sticky;
background: #fefefe;
z-index: 999;
}
.isScroll {
border-bottom: 0.5px solid #0000000F;
}
.listTopFilterWrapper {
display: flex;
align-items: center;
margin-top: 5px;
margin-bottom: 10px;
padding-top: 10px;
padding-bottom: 10px;
gap: 5px;
}
.listContentWrapper {
padding: 0 5px;
}
.menuFilter {
padding: 0;
}

View File

@@ -1,29 +1,22 @@
import ListCard from "../../components/ListCard";
import ListCardSkeleton from "../../components/ListCardSkeleton";
import List from "../../components/List";
import Menu from "../../components/Menu";
import CityFilter from "../../components/CityFilter";
import SearchBar from "../../components/SearchBar";
import FilterPopup from "./FilterPopup";
import styles from "./index.module.scss";
import { useEffect } from "react";
import Taro, { useReachBottom } from "@tarojs/taro";
import Taro, { usePageScroll, useReachBottom } from "@tarojs/taro";
import { useListStore } from "@/store/listStore";
import {useGlobalState} from '@/store/global'
import { useGlobalState } from "@/store/global";
import { View } from "@tarojs/components";
import CustomerNavBar from "@/components/CustomNavbar";
import GuideBar from "@/components/GuideBar";
import { useDictionaryActions } from "@/store/dictionaryStore";
import ListContainer from "@/container/listContainer";
const ListPage = () => {
// 从 store 获取数据和方法
const store = useListStore() || {};
const { statusNavbarHeightInfo } = useGlobalState() || {}
const { getDictionaryValue } = useDictionaryActions() || {};
console.log('===getDictionaryValue', getDictionaryValue('court_type'));
// locationOptions 室内
// game_play 玩法
const { statusNavbarHeightInfo } = useGlobalState() || {};
const {
isShowFilterPopup,
error,
@@ -31,7 +24,6 @@ const ListPage = () => {
loading,
fetchMatches,
refreshMatches,
clearError,
updateState,
filterCount,
updateFilterOptions, // 更新筛选条件
@@ -40,8 +32,15 @@ const ListPage = () => {
distanceData,
quickFilterData,
distanceQuickFilter,
isScrollTop,
} = store;
usePageScroll((res) => {
if (res?.scrollTop > 0 && !isScrollTop) {
updateState({ isScrollTop: true });
}
});
useReachBottom(() => {
console.log("触底了");
// 调用 store 的加载更多方法
@@ -82,78 +81,6 @@ const ListPage = () => {
});
});
// 错误处理
useEffect(() => {
if (error) {
Taro.showToast({
title: error,
icon: "error",
duration: 2000,
});
// 3秒后自动清除错误
setTimeout(() => {
clearError();
}, 3000);
}
}, [error, clearError]);
// 加载状态显示
if (loading && matches.length === 0) {
return (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "200px",
fontSize: "14px",
color: "#999",
}}
>
<div style={{ marginBottom: "10px" }}>...</div>
<div style={{ fontSize: "12px", color: "#ccc" }}>
</div>
</div>
);
}
// 错误状态显示
if (error && matches.length === 0) {
return (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "200px",
fontSize: "14px",
color: "#999",
}}
>
<div style={{ marginBottom: "10px" }}></div>
<div style={{ marginBottom: "15px", fontSize: "12px", color: "#ccc" }}>
{error}
</div>
<button
onClick={() => fetchMatches()}
style={{
padding: "8px 16px",
fontSize: "12px",
color: "#fff",
backgroundColor: "#007aff",
border: "none",
borderRadius: "4px",
}}
>
</button>
</div>
);
}
const toggleShowPopup = () => {
updateState({ isShowFilterPopup: !isShowFilterPopup });
};
@@ -183,7 +110,14 @@ const ListPage = () => {
<CustomerNavBar />
<View className={styles.listPage}>
<View className={styles.listTopSearchWrapper}>
<View
className={`${styles.listTopSearchWrapper} ${
isScrollTop ? styles.isScroll : ""
}`}
style={{
top: statusNavbarHeightInfo?.totalHeight,
}}
>
<SearchBar
handleFilterIcon={toggleShowPopup}
isSelect={filterCount > 0}
@@ -227,26 +161,16 @@ const ListPage = () => {
</div>
</View>
<View className={styles.listContentWrapper}>
{/* 列表内容 */}
<List>
{!loading &&
matches.length > 0 &&
matches.map((match, index) => (
<ListCard key={match.id || index} {...match} />
))}
</List>
{/* 空状态 */}
{loading &&
matches.length === 0 &&
new Array(10).fill(0).map(() => {
return <ListCardSkeleton />;
})}
</View>
<ListContainer
data={matches}
loading={loading}
error={error}
reload={refreshMatches}
/>
</View>
<GuideBar currentPage='list' />
<GuideBar currentPage="list" />
</>
);
};

View File

@@ -63,25 +63,6 @@ const mockTennisMatches: TennisMatch[] = [
},
];
// 模拟数据变化
const generateDynamicData = (): TennisMatch[] => {
Promise.resolve((res) => {
setTimeout(res, 3000);
});
return mockTennisMatches.map((match) => ({
...match,
// 随机更新注册人数
registeredCount: Math.min(
match.maxCount,
Math.max(0, match.registeredCount + Math.floor(Math.random() * 3) - 1)
),
// 随机更新距离
distance: `${(Math.random() * 5 + 1).toFixed(1)}km`,
// 随机更新时间
dateTime: Math.random() > 0.5 ? match.dateTime : "今天下午3点 2小时",
}));
};
/**
* 获取网球比赛列表
* @param params 查询参数
@@ -116,47 +97,3 @@ export const refreshTennisMatches = async (): Promise<TennisMatch[]> => {
}
};
/**
* 获取比赛详情
* @param id 比赛ID
* @returns Promise<TennisMatch | null>
*/
export const getTennisMatchDetail = async (
id: string
): Promise<TennisMatch | null> => {
try {
console.log("API调用: getTennisMatchDetail", id);
// 模拟网络延迟
await delay(600 + Math.random() * 400);
// 模拟网络错误
if (simulateNetworkError()) {
throw new Error("获取详情失败,请稍后重试");
}
const match = mockTennisMatches.find((m) => m.id === id);
if (!match) {
throw new Error("比赛不存在");
}
console.log("API获取详情成功:", match.title);
return match;
} catch (error) {
console.error("API获取详情失败:", error);
throw error;
}
};
/**
* 模拟API统计信息
*/
export const getApiStats = () => {
return {
totalCalls: 0,
successRate: 0.95,
averageResponseTime: 800,
lastCallTime: new Date().toISOString(),
};
};

View File

@@ -0,0 +1,29 @@
<svg width="154" height="156" viewBox="0 0 154 156" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M70.5141 58.1865L20.5296 75.5933C18.4077 76.3332 16.5641 77.7066 15.2478 79.5279C13.9315 81.3492 13.2059 83.5306 13.1691 85.7775L12.4468 129.786C12.3598 135.083 17.8131 138.657 22.6311 136.462L101.297 100.624C105.519 98.7 106.861 93.3465 104.045 89.664L82.7893 61.866C81.3916 60.0379 79.4523 58.6979 77.2479 58.0372C75.0436 57.3764 72.6869 57.4287 70.5141 58.1865Z" fill="black"/>
<path d="M28.16 72.9105L15.1272 79.6335C10.0332 82.581 11.6157 81.888 8.8002 87.084C5.9847 92.2808 4.99695 101.604 3.6147 109.556L0.137695 130.648C0.362921 135.482 1.83934 140.174 4.42245 144.266C7.0077 148.364 9.4917 151.732 13.2942 153.114C17.0967 154.497 20.2084 152.768 20.2084 152.768L40.5965 143.537C45.2787 141.485 45.4594 134.858 40.9024 132.425L29.2459 127.243L30.323 94.644L36.0439 90.4133L28.16 72.9105Z" fill="#D0CDCD"/>
<path d="M13.194 105.796C18.7672 104.935 24.384 104.348 30.012 103.973L30.3202 94.6448L36.0427 90.4133L28.1595 72.9105L18.48 76.3673C13.455 79.278 11.337 80.5583 8.79971 86.3933C5.98721 91.5 4.99721 101.95 3.95996 107.481C7.41746 106.79 8.99471 106.444 13.194 105.796Z" fill="black"/>
<path d="M30.085 103.123L30.235 98.148" stroke="black" stroke-width="1.93275" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M46.4077 41.745L53.8664 62.4817C54.4896 64.2142 54.5482 65.9617 54.0787 67.6477C53.4667 70.5217 51.3209 73.0042 48.2961 74.0985C45.3276 75.1725 42.1521 74.6152 39.7394 72.8355C38.4171 71.7982 37.2944 70.4355 36.6711 68.703L29.2124 47.9662L46.4077 41.745Z" fill="#D0CDCD"/>
<path d="M44.9808 59.8203C55.6915 54.8273 60.4616 42.3451 55.6352 31.9407C50.8088 21.5363 38.2135 17.1496 27.5028 22.1427C16.7921 27.1357 12.0219 39.6179 16.8484 50.0223C21.6748 60.4267 34.2701 64.8134 44.9808 59.8203Z" fill="#D0CDCD"/>
<path d="M34.1503 49.9987C34.0176 50.0292 33.8922 50.0854 33.7812 50.1643C33.6702 50.2433 33.5759 50.3432 33.5036 50.4586C33.4313 50.574 33.3824 50.7025 33.3597 50.8367C33.337 50.971 33.341 51.1084 33.3715 51.2411C33.4019 51.3738 33.4582 51.4992 33.5371 51.6102C33.616 51.7212 33.716 51.8155 33.8313 51.8878C33.9467 51.9602 34.0752 52.0091 34.2094 52.0317C34.3437 52.0544 34.4811 52.0504 34.6138 52.02L34.1503 49.9987ZM46.1301 49.3792C46.2628 49.3487 46.3882 49.2924 46.4992 49.2135C46.6101 49.1345 46.7044 49.0345 46.7767 48.9191C46.849 48.8037 46.8979 48.6752 46.9205 48.5409C46.9431 48.4066 46.9391 48.2692 46.9086 48.1365C46.8781 48.0038 46.8218 47.8784 46.7428 47.7674C46.6639 47.6565 46.5638 47.5621 46.4484 47.4899C46.333 47.4176 46.2045 47.3687 46.0702 47.3461C45.936 47.3235 45.7985 47.3275 45.6658 47.358L46.1301 49.3792ZM34.6138 52.02L46.1301 49.3792L45.6658 47.358L34.1503 49.9987L34.6138 52.02Z" fill="black"/>
<path d="M23.3773 46.0965C23.94 47.6632 23.945 49.3761 23.3915 50.9462C22.838 52.5162 21.7599 53.8473 20.339 54.7147C18.6395 52.803 17.3075 50.505 16.3828 47.934C15.637 45.8656 15.2192 43.6934 15.1445 41.496C18.6275 40.8037 22.1713 42.7425 23.3773 46.0965Z" fill="#D0CDCD"/>
<path d="M20.3345 54.7147C19.907 54.933 19.4795 55.1505 19.0317 55.3132C18.9762 55.3335 18.92 55.3537 18.8442 55.3177C18.584 55.4752 18.284 55.5202 17.984 55.566C14.5572 56.2387 11.0907 54.3345 9.86521 50.9235C8.63971 47.5125 10.154 43.8045 13.2747 42.105C13.535 41.9482 13.9265 41.8065 14.2062 41.7045L14.2625 41.6842C14.5422 41.583 14.8422 41.538 15.122 41.4367L15.2337 41.3955L15.254 41.4517C15.2427 41.772 15.2667 42.0157 15.311 42.3157C15.3402 42.7477 15.3125 43.2 15.4535 43.5915C15.5105 44.4555 15.7205 45.3907 15.9657 46.2495C16.0902 46.773 16.2912 47.3325 16.472 47.8357C17.3045 50.5042 18.6357 52.8022 20.3345 54.7147ZM63.3072 31.6725C64.5327 35.0835 63.0185 38.7915 60.0095 40.4497C59.75 40.6072 59.4897 40.7647 59.1897 40.8097C59.1545 40.8862 59.0982 40.9065 59.042 40.9267C58.5942 41.0887 58.0707 41.2155 57.659 41.3017C57.6387 41.2455 57.695 41.2252 57.695 41.2252C57.662 40.605 57.6845 39.9652 57.6515 39.345C57.6226 38.6914 57.5575 38.0399 57.4565 37.3935C57.2356 36.0767 56.8975 34.7823 56.4462 33.5257C56.1241 32.6284 55.7461 31.7522 55.3145 30.9022C55.1577 30.6427 55.0775 30.4192 54.941 30.216C54.725 29.7885 54.452 29.382 54.1797 28.9755C54.1392 28.863 54.0432 28.7715 53.9472 28.68C53.8022 28.4302 53.6329 28.1953 53.4417 27.9787C53.402 27.8662 53.3255 27.831 53.2857 27.7192L53.3975 27.6787C53.657 27.5212 53.9367 27.42 54.2165 27.3187L54.2727 27.2985C54.7624 27.1215 55.2707 27.0007 55.7877 26.9385C58.9392 26.556 62.1425 28.4295 63.3072 31.6725Z" fill="#D0CDCD"/>
<path d="M30.3822 24.198C36.524 21.9757 39.716 15.2062 37.5125 9.07798C35.3082 2.95048 28.5432 -0.21602 22.4015 2.00623C16.2597 4.22848 13.0677 10.9972 15.272 17.1255C17.4755 23.253 24.2412 26.4202 30.3822 24.198Z" fill="black"/>
<path d="M53.7928 28.674C52.4803 29.781 50.9555 30.6487 49.274 31.257C42.3238 33.7717 34.6468 30.921 30.947 24.8625C31.9565 31.8945 27.9178 38.9827 20.9675 41.4975C20.4628 41.6797 19.9378 41.8065 19.4338 41.9887C18.0283 42.3075 16.6183 42.4387 15.224 42.4372C15.179 42.1372 15.1348 41.8372 15.1663 41.5725L15.146 41.517C14.903 32.5635 20.321 24.027 29.1778 20.8237L29.2333 20.8027C29.402 20.7427 29.5138 20.7015 29.702 20.697C38.1703 17.823 47.3173 20.7105 52.7248 27.2895C52.841 27.4372 52.9775 27.6412 53.0945 27.7882C53.1905 27.8805 53.2108 27.936 53.2513 28.0477C53.3473 28.1392 53.4635 28.2877 53.504 28.3987C53.6563 28.4707 53.6968 28.5825 53.7928 28.674Z" fill="black"/>
<path d="M98.8938 145.212C124.344 145.212 144.976 124.58 144.976 99.1297C144.976 73.6792 124.344 53.0475 98.8938 53.0475C73.4433 53.0475 52.8115 73.6792 52.8115 99.1297C52.8115 124.58 73.4433 145.212 98.8938 145.212Z" fill="white"/>
<path d="M96.612 42.1425C65.2995 42.1425 39.9165 67.5255 39.9165 98.838C39.9165 130.151 65.2995 155.534 96.612 155.534C127.925 155.534 153.308 130.151 153.308 98.838C153.308 67.5255 127.925 42.1425 96.612 42.1425ZM133.19 91.4085V92.9925C133.19 93.6505 133.007 94.2955 132.661 94.8551C132.315 95.4148 131.82 95.8671 131.231 96.1613L127.713 97.9215C127.154 98.2011 126.531 98.3279 125.908 98.2889C125.284 98.2499 124.682 98.0466 124.163 97.6995L119.999 94.9245C119.55 94.6257 119.039 94.4331 118.504 94.3614C117.969 94.2896 117.425 94.3407 116.913 94.5105L116.307 94.7115C114.09 95.4503 113.184 98.0933 114.481 100.039L117.508 104.579C117.831 105.064 118.269 105.462 118.783 105.737C119.297 106.012 119.871 106.156 120.455 106.156H122.331C123.271 106.156 124.172 106.529 124.837 107.194C125.501 107.858 125.875 108.76 125.875 109.7V112.292C125.875 113.059 125.626 113.805 125.166 114.419L120.882 120.129C120.557 120.563 120.336 121.066 120.235 121.599L119.252 126.818C119.109 127.571 118.728 128.257 118.163 128.775C115.997 130.759 114.078 132.998 112.448 135.442L109.469 139.911C108.853 140.835 108.004 141.582 107.009 142.075C106.013 142.569 104.906 142.792 103.797 142.723C102.688 142.654 101.616 142.295 100.689 141.682C99.7628 141.069 99.0134 140.223 98.5163 139.229C97.2638 136.724 96.6118 133.962 96.612 131.162V124.328C96.612 123.389 96.2387 122.487 95.5743 121.823C94.9098 121.159 94.0087 120.785 93.069 120.785H87.1523C83.8401 120.785 80.6637 119.469 78.321 117.128C75.9797 114.785 74.6641 111.608 74.6633 108.296V105.082C74.6637 103.143 75.1153 101.231 75.9822 99.4972C76.8492 97.7632 78.1077 96.2547 79.6583 95.091L85.9635 90.3615C88.1256 88.7401 90.755 87.8633 93.4575 87.8625H93.6608C95.5995 87.8625 97.5128 88.3133 99.246 89.1818L102.611 90.864C103.027 91.0723 103.48 91.1965 103.944 91.2294C104.408 91.2624 104.874 91.2035 105.316 91.056L116.132 87.4508C116.93 87.1853 117.608 86.6442 118.044 85.9242C118.48 85.2043 118.646 84.3526 118.511 83.5218C118.376 82.691 117.95 81.9353 117.308 81.3902C116.667 80.8451 115.853 80.5462 115.011 80.547H112.704C112.239 80.547 111.778 80.4554 111.348 80.2773C110.918 80.0991 110.528 79.8381 110.199 79.509L108.617 77.9273C108.287 77.5981 107.897 77.337 107.467 77.1589C107.037 76.9808 106.576 76.8892 106.111 76.8893H85.5248C84.5849 76.8893 83.6835 76.5159 83.0189 75.8513C82.3544 75.1867 81.981 74.2854 81.981 73.3455V72.3398C81.9811 71.5495 82.2453 70.7819 82.7317 70.159C83.218 69.5361 83.8986 69.0936 84.6653 68.9018L87.9683 68.076C88.8236 67.8622 89.5686 67.3373 90.0578 66.6038L91.905 63.8355C92.2283 63.3503 92.6664 62.9524 93.1805 62.6772C93.6946 62.4021 94.2687 62.2582 94.8518 62.2583H100.387C101.326 62.2581 102.228 61.8846 102.892 61.2201C103.557 60.5555 103.93 59.6542 103.93 58.7145V53.7533C122.041 56.6888 136.588 70.266 140.947 87.8648H136.734C136.269 87.8648 135.808 87.9564 135.378 88.1345C134.948 88.3126 134.557 88.5736 134.228 88.9027C133.899 89.2318 133.638 89.6224 133.46 90.0524C133.282 90.4823 133.19 90.9431 133.19 91.4085Z" fill="#747474"/>
<path d="M37.9613 129.181C35.6475 126.733 35.763 122.86 38.211 120.546L44.4563 114.671C46.9043 112.358 50.7773 112.473 53.0903 114.921C55.404 117.369 55.2885 121.242 52.8405 123.556L46.596 129.431C44.1473 131.744 40.2743 131.629 37.9613 129.181Z" fill="#D0CDCD"/>
<path d="M36.5264 122.911L42.9441 116.122M47.4921 129.153L53.9376 122.37" stroke="#D0CDCD" stroke-width="3.02325" stroke-miterlimit="10"/>
<path d="M11.9363 149.623L8.238 144.182C5.50275 140.194 6.51375 134.734 10.5322 132.026L62.5583 98.1885C66.5468 95.4532 62.6385 102.442 65.346 106.461L70.416 112.15C73.1512 116.14 80.1368 115.372 76.1183 118.08L24.093 151.917C20.1608 154.651 14.6723 153.611 11.9363 149.623Z" fill="#B0B0B0"/>
<g style="mix-blend-mode:darken">
<path d="M55.1377 106.668C55.5112 107.569 55.9417 108.467 56.343 109.339L56.373 109.366C56.5237 109.618 56.6182 109.871 56.7697 110.123C58.569 113.459 60.5947 116.73 62.7007 119.855C63.843 121.529 64.9852 123.203 66.1852 124.875L76.116 118.081C80.1045 115.345 73.4295 117.829 70.6942 113.84L65.6685 106.437C62.9332 102.448 66.5437 95.4547 62.5552 98.19L54.0742 103.963C54.42 104.893 54.7642 105.767 55.1377 106.668Z" fill="black" fill-opacity="0.8"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M140.91 116.307C122.696 135.819 92.1135 136.87 72.6015 118.656C53.0902 100.441 52.0387 69.8587 70.2532 50.3475C88.4677 30.8362 119.05 29.7847 138.562 47.9985C158.073 66.213 159.124 96.7957 140.91 116.307ZM134.107 109.957C119.401 125.711 94.707 126.56 78.9532 111.853C63.1995 97.1467 62.3505 72.4537 77.0572 56.7C91.764 40.9462 116.457 40.0972 132.211 54.8032C147.964 69.51 148.813 94.2037 134.107 109.957Z" fill="#B0B0B0"/>
<path d="M15.1042 141.32C9.61571 134.262 16.758 113.179 36.5242 121.147L52.9132 129.966C57.1837 131.926 54.6195 136.279 50.424 138.399L31.275 151.165L24.5145 147.595C20.5275 145.762 17.6407 144.901 15.1042 141.32Z" fill="#D0CDCD"/>
<path d="M37.1651 150.753C36.2621 151.344 35.218 151.684 34.1401 151.738C33.0621 151.792 31.9892 151.558 31.0316 151.06L18.1241 144.359C16.7218 143.596 15.6748 142.313 15.2084 140.786C14.7419 139.259 14.8931 137.61 15.6295 136.194C16.366 134.777 17.6288 133.706 19.1466 133.211C20.6643 132.716 22.3159 132.836 23.7461 133.545L36.6529 140.246C38.0787 140.987 39.1539 142.261 39.6444 143.792C40.1348 145.322 40.0006 146.984 39.2711 148.415C38.7887 149.363 38.0668 150.168 37.1771 150.75L37.1651 150.753Z" fill="#D0CDCD"/>
<path d="M44.4604 145.995C43.557 146.586 42.5127 146.925 41.4347 146.979C40.3566 147.034 39.2836 146.8 38.3255 146.303L25.4187 139.603C23.9855 138.858 22.9067 137.575 22.4195 136.035C21.9323 134.495 22.0766 132.824 22.8207 131.391C23.5658 129.958 24.8489 128.88 26.3885 128.392C27.1508 128.15 27.9534 128.061 28.7501 128.13C29.5468 128.199 30.3221 128.425 31.0317 128.794L43.9392 135.494C45.3644 136.236 46.4392 137.51 46.9298 139.039C47.4204 140.569 47.2871 142.231 46.5589 143.662C46.0767 144.61 45.3544 145.415 44.4642 145.997L44.4604 145.995Z" fill="#D0CDCD"/>
<path d="M53.9901 140.69C53.0871 141.281 52.043 141.621 50.9651 141.675C49.8871 141.73 48.8142 141.496 47.8566 140.998L34.9491 134.297C33.5518 133.533 32.5093 132.252 32.045 130.729C31.5806 129.205 31.7313 127.561 32.4649 126.147C33.1984 124.733 34.4564 123.663 35.9693 123.166C37.4823 122.668 39.1299 122.783 40.5591 123.486L53.4659 130.186C54.4098 130.677 55.209 131.406 55.7841 132.301C56.3582 133.196 56.688 134.226 56.7409 135.288C56.7938 136.351 56.5679 137.408 56.0856 138.356C55.6029 139.304 54.8804 140.108 53.9901 140.69Z" fill="#D0CDCD"/>
<path d="M47.8516 140.992L38.3513 136.055M40.2143 147.141L30.7148 142.205" stroke="black" stroke-width="1.93275" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M30.8362 119.368C26.5027 118.438 17.1997 118.786 14.6572 127.624" stroke="black" stroke-width="1.383" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M81.9273 0.737993C80.8564 0.554875 79.7567 0.804308 78.8696 1.4315C77.9825 2.0587 77.3807 3.01236 77.1963 4.08299L72.049 31.2427C71.9848 31.6183 72.0334 32.0045 72.1886 32.3525C72.3438 32.7005 72.5987 32.9946 72.921 33.1979C73.2433 33.4011 73.6186 33.5042 73.9995 33.4942C74.3804 33.4842 74.7498 33.3616 75.061 33.1417L83.524 27.828L100.171 30.7132C101.242 30.8966 102.342 30.6472 103.229 30.02C104.116 29.3928 104.718 28.439 104.902 27.3682L108.046 9.40349C108.137 8.87348 108.123 8.33065 108.005 7.80603C107.886 7.28142 107.666 6.78531 107.355 6.34607C107.045 5.90683 106.651 5.53307 106.196 5.24616C105.741 4.95925 105.234 4.76482 104.704 4.67399L81.9273 0.737993ZM91.5595 10.788C90.7143 10.5652 89.7873 10.8697 88.945 11.844C88.6653 12.168 88.1898 12.2745 87.8313 12.0397L85.666 10.6237C85.3068 10.389 85.204 9.90599 85.4665 9.56699C86.9883 7.60649 89.494 5.96249 92.611 6.78299C94.2112 7.20481 95.5784 8.24469 96.4122 9.6741C97.246 11.1035 97.4782 12.8055 97.0578 14.406C96.5478 16.3485 94.9428 17.6392 93.4518 18.3322C92.1603 18.9322 90.589 19.2622 89.107 19.071C88.6818 19.0162 88.4335 18.5865 88.543 18.1725L89.2 15.669C89.3088 15.255 89.74 15.003 90.1675 14.9775C90.7011 14.9399 91.2231 14.8041 91.7073 14.577C92.6058 14.1592 92.9808 13.6372 93.055 13.3522C93.1967 12.8138 93.1188 12.2411 92.8383 11.7601C92.5579 11.2791 92.0979 10.9299 91.5595 10.788ZM86.89 21.573C87.208 20.3655 88.4073 19.7752 89.5623 20.0797C90.7428 20.3902 91.5205 21.5025 91.2025 22.7085C90.8853 23.916 89.6538 24.5265 88.4733 24.2152C87.3183 23.9107 86.572 22.7797 86.89 21.573Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,34 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1261_18963)">
<g clip-path="url(#clip0_1261_18963)">
<rect x="3" y="2" width="14" height="14" rx="7" fill="#21B200"/>
<path d="M9.99996 14.8333C13.2216 14.8333 15.8333 12.2216 15.8333 8.99996C15.8333 5.7783 13.2216 3.16663 9.99996 3.16663C6.7783 3.16663 4.16663 5.7783 4.16663 8.99996C4.16663 12.2216 6.7783 14.8333 9.99996 14.8333Z" stroke="#21B200"/>
<path d="M9.99996 3.16663C9.97053 5.11152 9.49301 6.57026 8.56741 7.54291C7.64178 8.51553 6.17484 9.00203 4.16663 9.00238" stroke="#21B200" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.8241 9.29315C13.9246 9.16286 12.4732 9.56886 11.4698 10.5111C10.4665 11.4534 9.97684 12.8942 10.0008 14.8333" stroke="#21B200" stroke-linecap="round"/>
<g clip-path="url(#clip1_1261_18963)">
<path d="M9.99992 13.1666C12.3011 13.1666 14.1666 11.3011 14.1666 8.99992C14.1666 6.69873 12.3011 4.83325 9.99992 4.83325C7.69873 4.83325 5.83325 6.69873 5.83325 8.99992C5.83325 11.3011 7.69873 13.1666 9.99992 13.1666Z" stroke="white"/>
<path d="M9.99992 4.83325C9.9789 6.22246 9.63781 7.26442 8.97667 7.95917C8.3155 8.6539 7.26769 9.0014 5.83325 9.00165" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.16 9.20934C12.8033 9.11628 11.7666 9.40628 11.0499 10.0793C10.3332 10.7524 9.98346 11.7815 10.0006 13.1666" stroke="white" stroke-linecap="round"/>
</g>
</g>
<rect x="2.5" y="1.5" width="15" height="15" rx="7.5" stroke="white"/>
</g>
<defs>
<filter id="filter0_d_1261_18963" x="0" y="0" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1261_18963"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1261_18963" result="shape"/>
</filter>
<clipPath id="clip0_1261_18963">
<rect x="3" y="2" width="14" height="14" rx="7" fill="white"/>
</clipPath>
<clipPath id="clip1_1261_18963">
<rect width="10" height="10" fill="white" transform="translate(5 4)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.2426 12.7426C11.1569 13.8284 9.65687 14.5 8 14.5C4.6863 14.5 2 11.8137 2 8.5C2 5.1863 4.6863 2.5 8 2.5C9.65687 2.5 11.1569 3.17157 12.2426 4.25737C12.7953 4.81003 14 6.16667 14 6.16667" stroke="black" stroke-opacity="0.85" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 3.16669V6.16669H11" stroke="black" stroke-opacity="0.85" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 553 B

View File

@@ -1,6 +1,6 @@
import { create } from 'zustand'
import { getTennisMatches } from '../services/listApi'
import {ListActions, IFilterOptions, ListState } from '../../types/list/types'
import { ListActions, IFilterOptions, ListState } from '../../types/list/types'
// 完整的 Store 类型
type TennisStore = ListState & ListActions
@@ -40,7 +40,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
{ id: 3, label: "10km", value: "10km" },
],
// 快捷筛选数据
quickFilterData:[
quickFilterData: [
{ text: "默认排序", value: "0" },
{ text: "好评排序", value: "1" },
{ text: "销量排序", value: "2" },
@@ -75,6 +75,8 @@ export const useListStore = create<TennisStore>()((set, get) => ({
],
// 球局数量
gamesNum: 124,
// 页面滚动距离顶部距离 是否大于0
isScrollTop: false,
// 获取比赛数据
fetchMatches: async (params) => {
@@ -83,15 +85,42 @@ export const useListStore = create<TennisStore>()((set, get) => ({
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: rows || [],
matches: list || rows || [],
loading: false,
// lastRefreshTime: new Date().toISOString()
gamesNum: count,
})
} catch (error) {
set({
error,
matches: [],
loading: false,
})
}
},
@@ -100,13 +129,14 @@ export const useListStore = create<TennisStore>()((set, get) => ({
set({ loading: true, error: null })
try {
const matches = await getTennisMatches()
const resData = await getTennisMatches() || {};
const { data = {}, code } = resData;
const { count, rows } = data;
set({
matches,
matches: rows,
loading: false,
lastRefreshTime: new Date().toISOString()
})
console.log('Store: 成功刷新网球比赛数据:', matches.length, '条')
} catch (error) {
}
},

View File

@@ -1,6 +1,6 @@
// 网球比赛数据接口
export interface TennisMatch {
id: string
id: number
title: string
dateTime: string
location: string
@@ -39,6 +39,7 @@ export interface ListState {
locationOptions: BubbleOption[]
gamePlayOptions: BubbleOption[]
gamesNum: number
isScrollTop: boolean
}
export interface ListState {