列表联调
This commit is contained in:
@@ -6,9 +6,6 @@ import { useDictionaryStore } from './store/dictionaryStore'
|
|||||||
import { useGlobalStore } from './store/global'
|
import { useGlobalStore } from './store/global'
|
||||||
import { check_login_status } from './services/loginService';
|
import { check_login_status } from './services/loginService';
|
||||||
|
|
||||||
|
|
||||||
// import { getNavbarHeight } from "@/utils/getNavbarHeight";
|
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
}
|
}
|
||||||
@@ -31,6 +28,7 @@ class App extends Component<AppProps> {
|
|||||||
// 初始化字典数据
|
// 初始化字典数据
|
||||||
this.initDictionaryData()
|
this.initDictionaryData()
|
||||||
this.getNavBarHeight()
|
this.getNavBarHeight()
|
||||||
|
this.getLocation()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidShow() { }
|
componentDidShow() { }
|
||||||
@@ -51,7 +49,12 @@ class App extends Component<AppProps> {
|
|||||||
getNavBarHeight = () => {
|
getNavBarHeight = () => {
|
||||||
const { getNavbarHeightInfo } = useGlobalStore.getState()
|
const { getNavbarHeightInfo } = useGlobalStore.getState()
|
||||||
getNavbarHeightInfo()
|
getNavbarHeightInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取位置信息
|
||||||
|
getLocation = () => {
|
||||||
|
const { getCurrentLocationInfo } = useGlobalStore.getState()
|
||||||
|
getCurrentLocationInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
}
|
}
|
||||||
.arrow {
|
.arrow {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
|
height: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.arrow.left {
|
.arrow.left {
|
||||||
|
|||||||
@@ -35,18 +35,18 @@
|
|||||||
border-bottom-right-radius: 30px;
|
border-bottom-right-radius: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nut-menu-container-item {
|
// .nut-menu-container-item {
|
||||||
color: rgba(60, 60, 67, 0.60);
|
// color: rgba(60, 60, 67, 0.6);
|
||||||
font-size: 14px;
|
// font-size: 14px;
|
||||||
font-weight: 600;
|
// font-weight: 600;
|
||||||
line-height: 20px;
|
// line-height: 20px;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.nut-menu-container-item.active {
|
// .nut-menu-container-item.active {
|
||||||
flex-direction: row-reverse;
|
// flex-direction: row-reverse;
|
||||||
justify-content: space-between;
|
// justify-content: space-between;
|
||||||
color: #000;
|
// color: #000;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.positionWrap {
|
.positionWrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -88,4 +88,40 @@
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quickOptionsWrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.quickItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0;
|
||||||
|
color: rgba(60, 60, 67, 0.6);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
&.active {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.distanceQuickFilterWrap_0 .nut-menu-title-0 {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
&.active {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.distanceQuickFilterWrap_1 .nut-menu-title-1 {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
&.active {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Menu, Button } from "@nutui/nutui-react-taro";
|
import { Menu, Button } from "@nutui/nutui-react-taro";
|
||||||
import { Image } from "@tarojs/components";
|
import { Image, View, Text } from "@tarojs/components";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
import Bubble from "../Bubble";
|
import Bubble from "../Bubble";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
@@ -15,23 +15,46 @@ const Demo3 = (props) => {
|
|||||||
cityValue,
|
cityValue,
|
||||||
quickValue,
|
quickValue,
|
||||||
} = props;
|
} = props;
|
||||||
|
const cityRef = useRef(null);
|
||||||
|
const quickRef = useRef(null);
|
||||||
|
const [changePosition, setChangePosition] = useState<number[]>([]);
|
||||||
|
|
||||||
const itemRef = useRef(null);
|
// 全城筛选显示的标题
|
||||||
|
const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
|
||||||
|
|
||||||
const handleChange = (name: string, value: string) => {
|
// 快捷筛选显示的标题
|
||||||
|
const quickTitle = quickOptions.find(
|
||||||
|
(item) => item.value === quickValue
|
||||||
|
)?.label;
|
||||||
|
|
||||||
|
// className
|
||||||
|
const filterWrapperClassName = changePosition.reduce((pre, cur) => {
|
||||||
|
return `${pre} distanceQuickFilterWrap_${cur}`;
|
||||||
|
}, "");
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
name: string,
|
||||||
|
value: string | number,
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
|
setChangePosition((preState) => {
|
||||||
|
const newData = new Set([...preState, index]);
|
||||||
|
return Array.from(newData);
|
||||||
|
});
|
||||||
onChange && onChange(name, value);
|
onChange && onChange(name, value);
|
||||||
(itemRef.current as any)?.toggle(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
|
// 控制隐藏
|
||||||
|
index === 0 && (cityRef.current as any)?.toggle(false);
|
||||||
|
index === 1 && (quickRef.current as any)?.toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
className="distanceQuickFilterWrap"
|
className={`distanceQuickFilterWrap ${filterWrapperClassName}`}
|
||||||
>
|
>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
title={cityValue}
|
title={cityTitle}
|
||||||
ref={itemRef}
|
ref={cityRef}
|
||||||
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
|
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
|
||||||
>
|
>
|
||||||
<div className="positionWrap">
|
<div className="positionWrap">
|
||||||
@@ -42,7 +65,7 @@ const Demo3 = (props) => {
|
|||||||
<Bubble
|
<Bubble
|
||||||
options={cityOptions}
|
options={cityOptions}
|
||||||
value={cityValue}
|
value={cityValue}
|
||||||
onChange={handleChange}
|
onChange={(name, value) => handleChange(name, value, 0)}
|
||||||
layout="grid"
|
layout="grid"
|
||||||
size="small"
|
size="small"
|
||||||
columns={4}
|
columns={4}
|
||||||
@@ -52,30 +75,35 @@ const Demo3 = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
options={quickOptions}
|
title={quickTitle}
|
||||||
|
ref={quickRef}
|
||||||
defaultValue={quickValue}
|
defaultValue={quickValue}
|
||||||
onChange={(value) => handleChange(quickName, value)}
|
// options={quickOptions}
|
||||||
icon={<Image className="itemIcon" src={img.ICON_MENU_ITEM_SELECTED} />}
|
// onChange={(value) => handleChange(quickName, value, 1)}
|
||||||
|
// icon={<Image className="itemIcon" src={img.ICON_MENU_ITEM_SELECTED} />}
|
||||||
|
>
|
||||||
|
<View className="quickOptionsWrapper">
|
||||||
|
{quickOptions.map((item) => {
|
||||||
|
const active = quickValue === item?.value;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className={`quickItem ${active && "active"}`}
|
||||||
|
onClick={() => handleChange(quickName, item.value, 1)}
|
||||||
|
>
|
||||||
|
<View>{item?.label}</View>
|
||||||
|
{active && (
|
||||||
|
<View>
|
||||||
|
<Image
|
||||||
|
className="itemIcon"
|
||||||
|
src={img.ICON_MENU_ITEM_SELECTED}
|
||||||
/>
|
/>
|
||||||
{/* <Menu.Item title="筛选" ref={itemRef}>
|
</View>
|
||||||
<div
|
)}
|
||||||
style={{
|
</View>
|
||||||
width: '50%',
|
);
|
||||||
lineHeight: '28px',
|
})}
|
||||||
padding: '0 30px',
|
</View>
|
||||||
}}
|
</Menu.Item>
|
||||||
>
|
|
||||||
自定义内容
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
;(itemRef.current as any)?.toggle(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
确认
|
|
||||||
</Button>
|
|
||||||
</Menu.Item> */}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,23 @@ import styles from "./index.module.scss";
|
|||||||
import { Button } from "@nutui/nutui-react-taro";
|
import { Button } from "@nutui/nutui-react-taro";
|
||||||
import { useListStore } from "src/store/listStore";
|
import { useListStore } from "src/store/listStore";
|
||||||
import { BubbleOption, FilterPopupProps } from "../../../types/list/types";
|
import { BubbleOption, FilterPopupProps } from "../../../types/list/types";
|
||||||
|
import CalendarCard from "@/components/CalendarCard/index";
|
||||||
|
import dateRangeUtils from '@/utils/dateRange'
|
||||||
|
|
||||||
// 场地
|
// 场地
|
||||||
import CourtType from "@/components/CourtType";
|
import CourtType from "@/components/CourtType";
|
||||||
// 玩法
|
// 玩法
|
||||||
import GamePlayType from "@/components/GamePlayType";
|
import GamePlayType from "@/components/GamePlayType";
|
||||||
import { useDictionaryActions } from "@/store/dictionaryStore";
|
import { useDictionaryActions } from "@/store/dictionaryStore";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { View } from "@tarojs/components";
|
||||||
|
|
||||||
|
|
||||||
|
const dateTrabseformMap = {
|
||||||
|
'1': dateRangeUtils.getThisWeekend,
|
||||||
|
'2': dateRangeUtils.getNextWeekRange,
|
||||||
|
'3': dateRangeUtils.getNextMonthRange
|
||||||
|
}
|
||||||
|
|
||||||
const FilterPopup = (props: FilterPopupProps) => {
|
const FilterPopup = (props: FilterPopupProps) => {
|
||||||
const {
|
const {
|
||||||
@@ -24,34 +35,75 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
onClose,
|
onClose,
|
||||||
statusNavbarHeigh,
|
statusNavbarHeigh,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const store = useListStore() || {};
|
const store = useListStore() || {};
|
||||||
const { getDictionaryValue } = useDictionaryActions() || {};
|
const { getDictionaryValue } = useDictionaryActions() || {};
|
||||||
const { timeBubbleData } = store;
|
const { timeBubbleData, gamesNum, dateRangeOptions } = store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 处理字典选项
|
||||||
|
* @param dictionaryValue 字典选项
|
||||||
|
* @returns 选项列表
|
||||||
|
*/
|
||||||
const handleOptions = (dictionaryValue: []) => {
|
const handleOptions = (dictionaryValue: []) => {
|
||||||
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
|
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 场地类型选项
|
||||||
|
*/
|
||||||
const courtType = getDictionaryValue("court_type") || [];
|
const courtType = getDictionaryValue("court_type") || [];
|
||||||
const locationOptions: BubbleOption[] = useMemo(() => {
|
const locationOptions: BubbleOption[] = useMemo(() => {
|
||||||
return courtType ? handleOptions(courtType) : [];
|
return courtType ? handleOptions(courtType) : [];
|
||||||
}, [courtType]);
|
}, [courtType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 玩法选项
|
||||||
|
*/
|
||||||
const gamePlay = getDictionaryValue("game_play") || [];
|
const gamePlay = getDictionaryValue("game_play") || [];
|
||||||
const gamePlayOptions = useMemo(() => {
|
const gamePlayOptions = useMemo(() => {
|
||||||
return gamePlay ? handleOptions(gamePlay) : [];
|
return gamePlay ? handleOptions(gamePlay) : [];
|
||||||
}, [gamePlay]);
|
}, [gamePlay]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 筛选选项改变
|
||||||
|
* @param name 选项名称
|
||||||
|
* @param value 选项值
|
||||||
|
*/
|
||||||
const handleFilterChange = (name, value) => {
|
const handleFilterChange = (name, value) => {
|
||||||
onChange({ [name]: value });
|
onChange({ [name]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 清空筛选
|
||||||
|
*/
|
||||||
const handleClearFilter = () => {
|
const handleClearFilter = () => {
|
||||||
onClear();
|
onClear();
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 日期选择
|
||||||
|
* @param date 日期
|
||||||
|
*/
|
||||||
|
const handleDateRangeChange = (date: Date) => {
|
||||||
|
onChange({
|
||||||
|
'dateRange': [date, date],
|
||||||
|
'dateRangeQuick': '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 点击 本周末 一周内 一月内
|
||||||
|
*/
|
||||||
|
const handleDateRangeQuickClick = (name, value) => {
|
||||||
|
const date = dateTrabseformMap?.[value]()
|
||||||
|
onChange({
|
||||||
|
'dateRange': [date?.start, date?.end],
|
||||||
|
[name]: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popup
|
<Popup
|
||||||
@@ -62,17 +114,38 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
style={{ marginTop: statusNavbarHeigh + "px" }}
|
style={{ marginTop: statusNavbarHeigh + "px" }}
|
||||||
overlayStyle={{ marginTop: statusNavbarHeigh + "px" }}
|
overlayStyle={{ marginTop: statusNavbarHeigh + "px" }}
|
||||||
|
zIndex={1001}
|
||||||
>
|
>
|
||||||
<div className={styles.filterPopupWrapper}>
|
<div className={styles.filterPopupWrapper}>
|
||||||
|
{/* 日历 */}
|
||||||
|
<View>
|
||||||
|
{/* 快捷选日期 */}
|
||||||
|
<View>
|
||||||
|
<Bubble
|
||||||
|
options={dateRangeOptions}
|
||||||
|
value={filterOptions?.dateRangeQuick}
|
||||||
|
onChange={handleDateRangeQuickClick}
|
||||||
|
layout="grid"
|
||||||
|
size="small"
|
||||||
|
columns={3}
|
||||||
|
name="dateRangeQuick"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<CalendarCard
|
||||||
|
value={filterOptions?.dateRange?.[0]}
|
||||||
|
// minDate={}
|
||||||
|
// maxDate={}
|
||||||
|
onChange={handleDateRangeChange} />
|
||||||
|
</View>
|
||||||
{/* 时间气泡选项 */}
|
{/* 时间气泡选项 */}
|
||||||
<Bubble
|
<Bubble
|
||||||
options={timeBubbleData}
|
options={timeBubbleData}
|
||||||
value={filterOptions?.time}
|
value={filterOptions?.timeSlot}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
layout="grid"
|
layout="grid"
|
||||||
size="small"
|
size="small"
|
||||||
columns={3}
|
columns={3}
|
||||||
name="time"
|
name="timeSlot"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 范围选择 */}
|
{/* 范围选择 */}
|
||||||
@@ -131,7 +204,7 @@ const FilterPopup = (props: FilterPopupProps) => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
>
|
>
|
||||||
显示 332 场球局
|
显示 {gamesNum} 场球局
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.location-position {
|
.location-position {
|
||||||
max-width: 66%;
|
max-width: 50%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
@@ -7,18 +7,23 @@ import "./index.scss";
|
|||||||
const ListCard: React.FC<ListCardProps> = ({
|
const ListCard: React.FC<ListCardProps> = ({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
dateTime,
|
start_time,
|
||||||
venue_description,
|
location,
|
||||||
distance_km,
|
distance_km,
|
||||||
registeredCount,
|
current_players,
|
||||||
maxCount,
|
max_players,
|
||||||
skillLevel,
|
skill_level_min,
|
||||||
|
skill_level_max,
|
||||||
play_type,
|
play_type,
|
||||||
images = [],
|
image_list = [],
|
||||||
court_type,
|
court_type,
|
||||||
|
key
|
||||||
}) => {
|
}) => {
|
||||||
const renderItemImage = (src: string) => {
|
const renderItemImage = (src: string) => {
|
||||||
return <Image src={src} className="image" mode="aspectFill" />;
|
return <Image src={src} className="image" mode="aspectFill"
|
||||||
|
lazyLoad
|
||||||
|
defaultSource='https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center'
|
||||||
|
/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewDetail = () => {
|
const handleViewDetail = () => {
|
||||||
@@ -30,21 +35,21 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
|
|
||||||
// 根据图片数量决定展示样式
|
// 根据图片数量决定展示样式
|
||||||
const renderImages = () => {
|
const renderImages = () => {
|
||||||
if (images?.length === 0) return null;
|
if (image_list?.length === 0) return null;
|
||||||
|
|
||||||
if (images?.length === 1) {
|
if (image_list?.length === 1) {
|
||||||
return (
|
return (
|
||||||
<View className="single-image">
|
<View className="single-image">
|
||||||
<View className="image-container">{renderItemImage(images[0])}</View>
|
<View className="image-container">{renderItemImage(image_list?.[0])}</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (images?.length === 2) {
|
if (image_list?.length === 2) {
|
||||||
return (
|
return (
|
||||||
<View className="double-image">
|
<View className="double-image">
|
||||||
<View className="image-container">{renderItemImage(images[0])}</View>
|
<View className="image-container">{renderItemImage(image_list?.[0])}</View>
|
||||||
<View className="image-container">{renderItemImage(images[1])}</View>
|
<View className="image-container">{renderItemImage(image_list?.[1])}</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,14 +57,14 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
// 3张或更多图片
|
// 3张或更多图片
|
||||||
return (
|
return (
|
||||||
<View className="triple-image">
|
<View className="triple-image">
|
||||||
<View className="image-container">{renderItemImage(images?.[0])}</View>
|
<View className="image-container">{renderItemImage(image_list?.[0])}</View>
|
||||||
<View className="image-container">{renderItemImage(images?.[1])}</View>
|
<View className="image-container">{renderItemImage(image_list?.[1])}</View>
|
||||||
<View className="image-container">{renderItemImage(images?.[2])}</View>
|
<View className="image-container">{renderItemImage(image_list?.[2])}</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<View className="listCard">
|
<View className="listCard" key={key}>
|
||||||
<View className="listItem" onClick={handleViewDetail}>
|
<View className="listItem" onClick={handleViewDetail}>
|
||||||
{/* 左侧内容区域 */}
|
{/* 左侧内容区域 */}
|
||||||
<View className="content">
|
<View className="content">
|
||||||
@@ -75,17 +80,17 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
|
|
||||||
{/* 时间信息 */}
|
{/* 时间信息 */}
|
||||||
<View className="date-time">
|
<View className="date-time">
|
||||||
<Text>{dateTime}</Text>
|
<Text>{start_time}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 地点,室内外,距离 */}
|
{/* 地点,室内外,距离 */}
|
||||||
|
|
||||||
<View className="location">
|
<View className="location">
|
||||||
{venue_description &&
|
{location &&
|
||||||
<Text className="location-text location-position">{venue_description}</Text>}
|
<Text className="location-text location-position">{location}</Text>}
|
||||||
<Text className="location-text location-time-distance">
|
<Text className="location-text location-time-distance">
|
||||||
{court_type && `・${court_type}`}
|
{court_type && `・${court_type}`}
|
||||||
{distance_km && `・${distance_km}`}
|
{distance_km && `・${distance_km}km`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -93,7 +98,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
<View className="bottom-info">
|
<View className="bottom-info">
|
||||||
<View className="left-section">
|
<View className="left-section">
|
||||||
<View className="avatar-group">
|
<View className="avatar-group">
|
||||||
{Array.from({ length: Math.min(registeredCount, 3) }).map(
|
{Array.from({ length: 3 }).map(
|
||||||
(_, index) => (
|
(_, index) => (
|
||||||
<View key={index} className="avatar">
|
<View key={index} className="avatar">
|
||||||
<Image
|
<Image
|
||||||
@@ -110,12 +115,12 @@ const ListCard: React.FC<ListCardProps> = ({
|
|||||||
<View className="tags">
|
<View className="tags">
|
||||||
<View className="tag">
|
<View className="tag">
|
||||||
<Text className="tag-text">
|
<Text className="tag-text">
|
||||||
报名人数 {registeredCount}/
|
报名人数 {current_players}/
|
||||||
<Text className="tag-text-max">{maxCount}</Text>
|
<Text className="tag-text-max">{max_players}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="tag">
|
<View className="tag">
|
||||||
<Text className="tag-text">{skill_level_max} zh {skill_level_max}</Text>
|
<Text className="tag-text">{skill_level_min} 至 {skill_level_max}</Text>
|
||||||
</View>
|
</View>
|
||||||
{play_type && <View className="tag">
|
{play_type && <View className="tag">
|
||||||
<Text className="tag-text">{play_type}</Text>
|
<Text className="tag-text">{play_type}</Text>
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import { Image, View, Text, Button } from "@tarojs/components";
|
|||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
|
|
||||||
const ListLoadError = ({ reload }: { reload: () => void }) => {
|
interface IProps {
|
||||||
|
reload?: () => void;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListLoadError = (props: IProps) => {
|
||||||
|
const { reload, text } = props;
|
||||||
const handleReload = () => {
|
const handleReload = () => {
|
||||||
reload && typeof reload === "function" && reload();
|
reload && typeof reload === "function" && reload();
|
||||||
};
|
};
|
||||||
@@ -13,11 +19,13 @@ const ListLoadError = ({ reload }: { reload: () => void }) => {
|
|||||||
className={styles.listLoadErrorImg}
|
className={styles.listLoadErrorImg}
|
||||||
src={img.ICON_LIST_LOAD_ERROR}
|
src={img.ICON_LIST_LOAD_ERROR}
|
||||||
/>
|
/>
|
||||||
<Text className={styles.listLoadErrorText}>加载失败</Text>
|
{text && <Text className={styles.listLoadErrorText}>{text}</Text>}
|
||||||
|
{reload && (
|
||||||
<Button className={styles.listLoadErrorBtn} onClick={handleReload}>
|
<Button className={styles.listLoadErrorBtn} onClick={handleReload}>
|
||||||
<Image src={img?.ICON_LIST_RELOAD} className={styles.reloadIcon} />
|
<Image src={img?.ICON_LIST_RELOAD} className={styles.reloadIcon} />
|
||||||
重试
|
重试
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ const NtrpRange: React.FC<RangeProps> = ({
|
|||||||
}, [JSON.stringify(value || [])]);
|
}, [JSON.stringify(value || [])]);
|
||||||
|
|
||||||
const handleChange = (val: [number, number]) => {
|
const handleChange = (val: [number, number]) => {
|
||||||
|
setCurrentValue(val);
|
||||||
|
// onChange?.(name, val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEndChange = (val: [number, number]) => {
|
||||||
setCurrentValue(val);
|
setCurrentValue(val);
|
||||||
onChange?.(name, val);
|
onChange?.(name, val);
|
||||||
};
|
};
|
||||||
@@ -76,7 +81,7 @@ const NtrpRange: React.FC<RangeProps> = ({
|
|||||||
max={max}
|
max={max}
|
||||||
step={step}
|
step={step}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
onEnd={handleChange}
|
onEnd={handleEndChange}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
defaultValue={[min, max]}
|
defaultValue={[min, max]}
|
||||||
|
|||||||
@@ -20,20 +20,20 @@ const ListHeader = (props: IProps) => {
|
|||||||
const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo;
|
const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo;
|
||||||
|
|
||||||
// 获取位置信息
|
// 获取位置信息
|
||||||
const getCurrentLocal = () => {
|
// const getCurrentLocal = () => {
|
||||||
updateState({
|
// updateState({
|
||||||
getLocationLoading: true,
|
// getLocationLoading: true,
|
||||||
});
|
// });
|
||||||
getCurrentLocation().then((res) => {
|
// getCurrentLocation().then((res) => {
|
||||||
updateState({
|
// updateState({
|
||||||
getLocationLoading: false,
|
// getLocationLoading: false,
|
||||||
location: res || {},
|
// location: res || {},
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
};
|
// };
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
getCurrentLocal();
|
// getCurrentLocal();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
const handleInputClick = () => {
|
const handleInputClick = () => {
|
||||||
Taro.navigateTo({
|
Taro.navigateTo({
|
||||||
|
|||||||
@@ -1,25 +1,33 @@
|
|||||||
import { View, Text } from "@tarojs/components";
|
import { View } from "@tarojs/components";
|
||||||
import ListCard from "@/components/ListCard";
|
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 { VirtualList } from '@nutui/nutui-react-taro'
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { useReachBottom } from "@tarojs/taro";
|
import { useReachBottom } from "@tarojs/taro";
|
||||||
|
|
||||||
const ListContainer = (props) => {
|
const ListContainer = (props) => {
|
||||||
const { loading, data = [], error, reload, recommendList } = props;
|
const {
|
||||||
|
loading,
|
||||||
|
data = [],
|
||||||
|
error,
|
||||||
|
reload,
|
||||||
|
recommendList,
|
||||||
|
loadMoreMatches,
|
||||||
|
} = props;
|
||||||
|
console.log("===data", data);
|
||||||
|
|
||||||
useReachBottom(() => {
|
useReachBottom(() => {
|
||||||
console.log("触底了");
|
console.log("触底了");
|
||||||
// 调用 store 的加载更多方法
|
// 调用 store 的加载更多方法
|
||||||
// loadMoreMatches();
|
loadMoreMatches();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ListLoadError reload={reload} />;
|
return <ListLoadError reload={reload} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderList = (list) => {
|
const renderSkeleton = () => {
|
||||||
if (loading && list.length === 0) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{new Array(10).fill(0).map(() => {
|
{new Array(10).fill(0).map(() => {
|
||||||
@@ -27,10 +35,38 @@ 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="暂无数据" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渲染数据
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* <VirtualList
|
||||||
|
containerHeight={1000}
|
||||||
|
itemHeight={144}
|
||||||
|
// itemEqual={false}
|
||||||
|
list={list}
|
||||||
|
itemRender={(data) => {
|
||||||
|
return <ListCard {...data}/>
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
{list?.map((match, index) => (
|
{list?.map((match, index) => (
|
||||||
<ListCard key={match.id || index} {...match} />
|
<ListCard key={match.id || index} {...match} />
|
||||||
))}
|
))}
|
||||||
@@ -41,12 +77,14 @@ const ListContainer = (props) => {
|
|||||||
return (
|
return (
|
||||||
<View className="listContentWrapper">
|
<View className="listContentWrapper">
|
||||||
{renderList(data)}
|
{renderList(data)}
|
||||||
<View className="recommendTextWrapper">
|
{/* 显示骨架屏 */}
|
||||||
|
{loading && renderSkeleton()}
|
||||||
|
{/* <View className="recommendTextWrapper">
|
||||||
<Text className="recommendText">搜索结果较少,已为你推荐其他内容</Text>
|
<Text className="recommendText">搜索结果较少,已为你推荐其他内容</Text>
|
||||||
</View>
|
</View>
|
||||||
{renderList(recommendList)}
|
{renderList(recommendList)} */}
|
||||||
{/* 到底了 */}
|
{/* 到底了 */}
|
||||||
<View className="bottomTextWrapper">到底了</View>
|
{data?.length > 0 && <View className="bottomTextWrapper">到底了</View>}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
.container {
|
|
||||||
padding-left: 17px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.line {
|
|
||||||
width: 1px;
|
|
||||||
height: 25px;
|
|
||||||
background-color: #0000000F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 60px;
|
|
||||||
height: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cityWrapper {
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.city {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoWrapper {
|
|
||||||
line-height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 12px;
|
|
||||||
color: #3C3C4399;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
144
src/container/listCustomNavbar/index.scss
Normal file
144
src/container/listCustomNavbar/index.scss
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
.listNavWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavContainer {
|
||||||
|
.listNavLine {
|
||||||
|
width: 1px;
|
||||||
|
height: 25px;
|
||||||
|
background-color: #0000000f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavLogo {
|
||||||
|
width: 60px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavChange {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavContentWrapper {
|
||||||
|
padding-left: 17px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavCityWrapper {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavCity {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoWrapper {
|
||||||
|
line-height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listNavInfoWrapper {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 12px;
|
||||||
|
color: #3c3c4399;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputCustomerNavbarContainer {
|
||||||
|
padding-left: 17px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 28px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon16 {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
width: 65%;
|
||||||
|
height: max-content;
|
||||||
|
// padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchContainer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5.85px;
|
||||||
|
padding: 7.8px;
|
||||||
|
height: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 30px;
|
||||||
|
border: 0.488px solid rgba(0, 0, 0, 0.06);
|
||||||
|
box-shadow: 0 3.902px 46.829px 0 rgba(0, 0, 0, 0.08);
|
||||||
|
height: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 13.659px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 17.561px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.nut-input {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputCustomerNavbarShowInput {
|
||||||
|
padding-left: 10px;
|
||||||
|
.logo {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleElement {
|
||||||
|
/* 绝对定位使两个元素重叠 */
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* 过渡动画设置,实现平滑切换 */
|
||||||
|
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第一个元素样式 */
|
||||||
|
.firstElement {
|
||||||
|
// background-color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第二个元素样式 */
|
||||||
|
.secondElement {
|
||||||
|
// background-color: #5cb85c;
|
||||||
|
/* 初始状态:透明且稍微偏移 */
|
||||||
|
opacity: 0;
|
||||||
|
// transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏状态 */
|
||||||
|
.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
// pointer-events: none; /* 隐藏时不响应鼠标事件 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可见状态 */
|
||||||
|
.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
@@ -1,65 +1,134 @@
|
|||||||
import { View, Text, Image } from "@tarojs/components";
|
import { View, Text, Image } from "@tarojs/components";
|
||||||
import img from "@/config/images";
|
import img from "@/config/images";
|
||||||
import { getCurrentLocation } from "@/utils/locationUtils";
|
|
||||||
import styles from "./index.module.scss";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useGlobalState } from "@/store/global";
|
import { useGlobalState } from "@/store/global";
|
||||||
import { useListState } from "@/store/listStore";
|
import { useListState } from "@/store/listStore";
|
||||||
import CustomNavbar from '@/components/CustomNavbar'
|
import CustomNavbar from "@/components/CustomNavbar";
|
||||||
|
import { Input } from "@nutui/nutui-react-taro";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
|
import "./index.scss";
|
||||||
|
|
||||||
const ListHeader = () => {
|
interface IProps {
|
||||||
|
config?: {
|
||||||
|
showInput: boolean;
|
||||||
|
inputLeftIcon: string;
|
||||||
|
iconPath?: string;
|
||||||
|
leftIconClick: () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListHeader = (props: IProps) => {
|
||||||
|
const { config } = props;
|
||||||
|
const {
|
||||||
|
showInput = false,
|
||||||
|
inputLeftIcon,
|
||||||
|
leftIconClick,
|
||||||
|
} = config || {};
|
||||||
const {
|
const {
|
||||||
updateState,
|
|
||||||
location,
|
location,
|
||||||
getLocationText,
|
getLocationText,
|
||||||
getLocationLoading,
|
getLocationLoading,
|
||||||
statusNavbarHeightInfo,
|
statusNavbarHeightInfo,
|
||||||
} = useGlobalState();
|
} = useGlobalState();
|
||||||
const { gamesNum } = useListState();
|
const { gamesNum, searchValue, isShowInputCustomerNavBar } = useListState();
|
||||||
const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo;
|
const { statusBarHeight, navbarHeight } = statusNavbarHeightInfo;
|
||||||
|
|
||||||
// 获取位置信息
|
|
||||||
const getCurrentLocal = () => {
|
|
||||||
updateState({
|
|
||||||
getLocationLoading: true,
|
|
||||||
});
|
|
||||||
getCurrentLocation().then((res) => {
|
|
||||||
updateState({
|
|
||||||
getLocationLoading: false,
|
|
||||||
location: res || {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
getCurrentLocal();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const currentAddress = getLocationLoading
|
const currentAddress = getLocationLoading
|
||||||
? getLocationText
|
? getLocationText
|
||||||
: location?.address;
|
: location?.address;
|
||||||
|
|
||||||
|
const handleInputClick = () => {
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
const currentPage = pages[pages.length - 1];
|
||||||
|
const currentPagePath = currentPage.route;
|
||||||
|
if (currentPagePath === "pages/searchResult/index") {
|
||||||
|
Taro.navigateBack();
|
||||||
|
} else {
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: "/pages/search/index",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击logo
|
||||||
|
const handleLogoClick = () => {
|
||||||
|
Taro.redirectTo({
|
||||||
|
url: "pages/list/index", // 列表页
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputLeftIconClick = () => {
|
||||||
|
if (leftIconClick) {
|
||||||
|
leftIconClick();
|
||||||
|
} else {
|
||||||
|
handleLogoClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomNavbar>
|
<CustomNavbar>
|
||||||
|
<View className="listNavWrapper">
|
||||||
|
{/* 首页logo 导航*/}
|
||||||
<View
|
<View
|
||||||
className={styles.container}
|
className={`listNavContainer toggleElement firstElement hidden ${
|
||||||
|
(!isShowInputCustomerNavBar && !showInput) && "visible"
|
||||||
|
}`}
|
||||||
style={{
|
style={{
|
||||||
height: `${navbarHeight}px`,
|
height: `${navbarHeight}px`,
|
||||||
paddingTop: `${statusBarHeight}px`,
|
paddingTop: `${statusBarHeight}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<View className="listNavContentWrapper">
|
||||||
{/* logo */}
|
{/* logo */}
|
||||||
<Image src={img.ICON_LOGO} className={styles.logo} />
|
<Image
|
||||||
<View className={styles.line} />
|
src={img.ICON_LOGO}
|
||||||
<View className={styles.content}>
|
className="listNavLogo"
|
||||||
<View className={styles.cityWrapper}>
|
onClick={handleLogoClick}
|
||||||
|
/>
|
||||||
|
<View className="listNavLine" />
|
||||||
|
<View className="listNavContent">
|
||||||
|
<View className="listNavCityWrapper">
|
||||||
{/* 位置 */}
|
{/* 位置 */}
|
||||||
<Text className={styles.city}>{currentAddress}</Text>
|
<Text className="listNavCity">{currentAddress}</Text>
|
||||||
{!getLocationLoading && (
|
{!getLocationLoading && currentAddress && (
|
||||||
<Image src={img.ICON_CHANGE} className={styles.change} />
|
<Image src={img.ICON_CHANGE} className="listNavChange" />
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View className={styles.infoWrapper}>
|
<View className="listNavInfoWrapper">
|
||||||
<Text className={styles.info}>附近${gamesNum}场球局</Text>
|
<Text className="listNavInfo">附近${gamesNum}场球局</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{/* 搜索导航 */}
|
||||||
|
<View
|
||||||
|
className={`inputCustomerNavbarContainer toggleElement secondElement hidden ${
|
||||||
|
(isShowInputCustomerNavBar || showInput) && "visible"
|
||||||
|
} ${showInput && "inputCustomerNavbarShowInput"}`}
|
||||||
|
style={{
|
||||||
|
height: `${navbarHeight}px`,
|
||||||
|
paddingTop: `${statusBarHeight}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className="navContent">
|
||||||
|
{/* logo */}
|
||||||
|
<Image
|
||||||
|
src={inputLeftIcon || img.ICON_LIST_INPUT_LOGO}
|
||||||
|
className="logo"
|
||||||
|
onClick={handleInputLeftIconClick}
|
||||||
|
/>
|
||||||
|
{/* 搜索框 */}
|
||||||
|
<View className="searchContainer">
|
||||||
|
<Image
|
||||||
|
className="searchIcon icon16"
|
||||||
|
src={img.ICON_LIST_SEARCH_SEARCH}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="搜索上海的球局和场地"
|
||||||
|
clearable={false}
|
||||||
|
value={searchValue}
|
||||||
|
onClick={handleInputClick}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -24,3 +24,53 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listNavWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleElement {
|
||||||
|
/* 绝对定位使两个元素重叠 */
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* 过渡动画设置,实现平滑切换 */
|
||||||
|
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第一个元素样式 */
|
||||||
|
.firstElement {
|
||||||
|
background-color: #4a90e2;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第二个元素样式 */
|
||||||
|
.secondElement {
|
||||||
|
background-color: #5cb85c;
|
||||||
|
color: white;
|
||||||
|
/* 初始状态:透明且稍微偏移 */
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可见状态 */
|
||||||
|
.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏状态 */
|
||||||
|
.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
pointer-events: none; /* 隐藏时不响应鼠标事件 */
|
||||||
|
}
|
||||||
@@ -7,17 +7,15 @@ 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";
|
||||||
import CustomerNavBar from "@/container/listCustomNavbar";
|
import CustomerNavBar from "@/container/listCustomNavbar";
|
||||||
import InputCustomerBar from "@/container/inputCustomerNavbar";
|
|
||||||
import GuideBar from "@/components/GuideBar";
|
import GuideBar from "@/components/GuideBar";
|
||||||
import ListContainer from "@/container/listContainer";
|
import ListContainer from "@/container/listContainer";
|
||||||
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
||||||
import img from "@/config/images";
|
|
||||||
|
|
||||||
const ListPage = () => {
|
const ListPage = () => {
|
||||||
// 从 store 获取数据和方法
|
// 从 store 获取数据和方法
|
||||||
const store = useListStore() || {};
|
const store = useListStore() || {};
|
||||||
|
|
||||||
const { statusNavbarHeightInfo, location } = useGlobalState() || {};
|
const { statusNavbarHeightInfo, location = {} } = useGlobalState() || {};
|
||||||
const { totalHeight } = statusNavbarHeightInfo || {};
|
const { totalHeight } = statusNavbarHeightInfo || {};
|
||||||
const {
|
const {
|
||||||
isShowFilterPopup,
|
isShowFilterPopup,
|
||||||
@@ -25,8 +23,7 @@ const ListPage = () => {
|
|||||||
matches,
|
matches,
|
||||||
recommendList,
|
recommendList,
|
||||||
loading,
|
loading,
|
||||||
fetchMatches,
|
getMatchesData,
|
||||||
refreshMatches,
|
|
||||||
updateState,
|
updateState,
|
||||||
filterCount,
|
filterCount,
|
||||||
updateFilterOptions, // 更新筛选条件
|
updateFilterOptions, // 更新筛选条件
|
||||||
@@ -38,50 +35,66 @@ const ListPage = () => {
|
|||||||
isScrollTop,
|
isScrollTop,
|
||||||
searchValue,
|
searchValue,
|
||||||
isShowInputCustomerNavBar,
|
isShowInputCustomerNavBar,
|
||||||
|
initialFilterSearch,
|
||||||
|
loadMoreMatches,
|
||||||
|
dateRangeOptions
|
||||||
} = store;
|
} = store;
|
||||||
|
|
||||||
usePageScroll((res) => {
|
usePageScroll((res) => {
|
||||||
if (res?.scrollTop >= totalHeight && !isScrollTop) {
|
if (res?.scrollTop >= totalHeight) {
|
||||||
!isShowInputCustomerNavBar && updateState({ isShowInputCustomerNavBar: true });
|
!isShowInputCustomerNavBar &&
|
||||||
|
updateState({ isShowInputCustomerNavBar: true });
|
||||||
} else {
|
} else {
|
||||||
isShowInputCustomerNavBar && updateState({ isShowInputCustomerNavBar: false });
|
isShowInputCustomerNavBar &&
|
||||||
|
updateState({ isShowInputCustomerNavBar: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 页面加载时获取数据
|
if (location?.address) {
|
||||||
fetchMatches();
|
|
||||||
// 保存位置
|
// 保存位置
|
||||||
updateState({ location });
|
updateState({ location });
|
||||||
}, []);
|
// 页面加载时获取数据
|
||||||
|
getMatchesData();
|
||||||
|
}
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const refreshMatches = () => {
|
||||||
|
initialFilterSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
// const getLoadMoreMatches = () => {
|
||||||
|
// loadMoreMatches()
|
||||||
|
// }
|
||||||
|
|
||||||
// 下拉刷新处理函数 - 使用Taro生命周期钩子
|
// 下拉刷新处理函数 - 使用Taro生命周期钩子
|
||||||
Taro.usePullDownRefresh(() => {
|
Taro.usePullDownRefresh(() => {
|
||||||
console.log("触发下拉刷新");
|
console.log("===触发下拉刷新");
|
||||||
|
clearFilterOptions()
|
||||||
|
|
||||||
// 调用 store 的刷新方法
|
// 调用 store 的刷新方法
|
||||||
refreshMatches()
|
// refreshMatches()
|
||||||
.then(() => {
|
// .then(() => {
|
||||||
// 刷新完成后停止下拉刷新动画
|
// // 刷新完成后停止下拉刷新动画
|
||||||
Taro.stopPullDownRefresh();
|
// Taro.stopPullDownRefresh();
|
||||||
|
|
||||||
// 显示刷新成功提示
|
// // 显示刷新成功提示
|
||||||
Taro.showToast({
|
// Taro.showToast({
|
||||||
title: "刷新成功",
|
// title: "刷新成功",
|
||||||
icon: "success",
|
// icon: "success",
|
||||||
duration: 1500,
|
// duration: 1500,
|
||||||
});
|
// });
|
||||||
})
|
// })
|
||||||
.catch(() => {
|
// .catch(() => {
|
||||||
// 刷新失败时也停止动画
|
// // 刷新失败时也停止动画
|
||||||
Taro.stopPullDownRefresh();
|
// Taro.stopPullDownRefresh();
|
||||||
|
|
||||||
Taro.showToast({
|
// Taro.showToast({
|
||||||
title: "刷新失败",
|
// title: "刷新失败",
|
||||||
icon: "error",
|
// icon: "error",
|
||||||
duration: 1500,
|
// duration: 1500,
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleShowPopup = () => {
|
const toggleShowPopup = () => {
|
||||||
@@ -115,21 +128,14 @@ const ListPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View>
|
||||||
{!isShowInputCustomerNavBar ? (
|
{/* 自定义导航 */}
|
||||||
<CustomerNavBar />
|
<CustomerNavBar />
|
||||||
) : (
|
|
||||||
<InputCustomerBar icon={img.ICON_LIST_INPUT_LOGO} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View className={styles.listPage}>
|
<View className={styles.listPage}>
|
||||||
<View
|
<View
|
||||||
className={`${styles.listTopSearchWrapper} ${
|
className={`${styles.listTopSearchWrapper} ${
|
||||||
isScrollTop ? styles.isScroll : ""
|
isScrollTop ? styles.isScroll : ""
|
||||||
}`}
|
}`}
|
||||||
// style={{
|
|
||||||
// top: statusNavbarHeightInfo?.totalHeight,
|
|
||||||
// }}
|
|
||||||
>
|
>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
handleFilterIcon={toggleShowPopup}
|
handleFilterIcon={toggleShowPopup}
|
||||||
@@ -176,11 +182,12 @@ const ListPage = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
reload={refreshMatches}
|
reload={refreshMatches}
|
||||||
|
loadMoreMatches={loadMoreMatches}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<GuideBar currentPage="list" />
|
<GuideBar currentPage="list" />
|
||||||
</>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.listSearchContainer {
|
.listSearchContainer {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
|
padding-top: 16px;
|
||||||
|
|
||||||
.icon16 {
|
.icon16 {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import CustomerNavbarBack from "@/components/CustomerNavbarBack";
|
// import CustomerNavbarBack from "@/components/CustomerNavbarBack";
|
||||||
import { View, Image, Text } from "@tarojs/components";
|
import { View, Image, Text } from "@tarojs/components";
|
||||||
import { Input } from "@nutui/nutui-react-taro";
|
import { Input } from "@nutui/nutui-react-taro";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
@@ -18,11 +18,13 @@ const ListSearch = () => {
|
|||||||
suggestionList,
|
suggestionList,
|
||||||
isShowSuggestion,
|
isShowSuggestion,
|
||||||
} = useListState() || {};
|
} = useListState() || {};
|
||||||
|
|
||||||
const ref = useRef<any>(null);
|
const ref = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getSearchHistory();
|
getSearchHistory();
|
||||||
|
return () => {
|
||||||
|
handleClear();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -35,6 +37,12 @@ const ListSearch = () => {
|
|||||||
return new RegExp(searchValue, "gi");
|
return new RegExp(searchValue, "gi");
|
||||||
}, [searchValue]);
|
}, [searchValue]);
|
||||||
|
|
||||||
|
// 是否显示清空图标
|
||||||
|
const isShowClearIcon = searchValue && searchValue?.length > 0;
|
||||||
|
|
||||||
|
// 是否显示搜索历史
|
||||||
|
const isShowHistory = searchHistory && searchHistory?.length > 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 输入
|
* @description 输入
|
||||||
* @param value
|
* @param value
|
||||||
@@ -54,16 +62,16 @@ const ListSearch = () => {
|
|||||||
* @description 点击清空输入内容
|
* @description 点击清空输入内容
|
||||||
*/
|
*/
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
updateState({ searchValue: "" });
|
updateState({ searchValue: "", isShowSuggestion: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 点击历史搜索
|
* @description 点击历史搜索
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
const handleHistoryClick = (value: string) => {
|
const handleHistoryClick = (item: { id: number; title: string }) => {
|
||||||
updateState({ searchValue: value });
|
updateState({ searchValue: item?.title });
|
||||||
handleSearch();
|
handleSearch(item?.title);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,25 +89,21 @@ const ListSearch = () => {
|
|||||||
searchValue: val,
|
searchValue: val,
|
||||||
isShowSuggestion: false,
|
isShowSuggestion: false,
|
||||||
});
|
});
|
||||||
handleSearch();
|
handleSearch(val);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 点击搜索
|
* @description 点击搜索
|
||||||
*/
|
*/
|
||||||
const handleSearch = () => {
|
const handleSearch = (val?: string) => {
|
||||||
|
if (!val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Taro.navigateTo({
|
Taro.navigateTo({
|
||||||
url: `/pages/searchResult/index`,
|
url: `/pages/searchResult/index`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 是否显示清空图标
|
|
||||||
const isShowClearIcon = searchValue && searchValue?.length > 0;
|
|
||||||
|
|
||||||
// 是否显示搜索历史
|
|
||||||
const isShowHistory =
|
|
||||||
!isShowClearIcon && searchHistory && searchHistory?.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View className="listSearchContainer">
|
<View className="listSearchContainer">
|
||||||
@@ -125,7 +129,7 @@ const ListSearch = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<View className="searchLine" />
|
<View className="searchLine" />
|
||||||
<Text className="searchText" onClick={handleSearch}>
|
<Text className="searchText" onClick={() => handleSearch(searchValue)}>
|
||||||
搜索
|
搜索
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -180,12 +184,15 @@ const ListSearch = () => {
|
|||||||
{isShowHistory && (
|
{isShowHistory && (
|
||||||
<View className="historySearchList">
|
<View className="historySearchList">
|
||||||
{(searchHistory || [])?.map((item) => {
|
{(searchHistory || [])?.map((item) => {
|
||||||
|
if (!item?.title) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
className="historySearchItem"
|
className="historySearchItem"
|
||||||
onClick={() => handleHistoryClick(item)}
|
onClick={() => handleHistoryClick(item)}
|
||||||
>
|
>
|
||||||
{item}
|
{item?.title}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '搜索结果',
|
navigationBarTitleText: '搜索结果',
|
||||||
// navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.searchResultFilterWrapper {
|
.searchResultFilterWrapper {
|
||||||
|
position: sticky;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { View, Image, Text } from "@tarojs/components";
|
import { View, Image, Text } from "@tarojs/components";
|
||||||
import { useSearchResultState } from "@/store/searchResultStore";
|
// import { useSearchResultState } from "@/store/searchResultStore";
|
||||||
|
import { useListStore } from "@/store/listStore";
|
||||||
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 img from "@/config/images";
|
||||||
|
import CustomerNavBar from "@/container/listCustomNavbar";
|
||||||
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";
|
import FilterPopup from "@/components/FilterPopup";
|
||||||
|
import "./index.scss";
|
||||||
|
import Taro from "@tarojs/taro";
|
||||||
|
|
||||||
const SearchResult = () => {
|
const SearchResult = () => {
|
||||||
const {
|
const {
|
||||||
isShowFilterPopup,
|
isShowFilterPopup,
|
||||||
error,
|
error,
|
||||||
distanceQuickFilter,
|
distanceQuickFilter,
|
||||||
matches,
|
searchResultData,
|
||||||
recommendList,
|
recommendList,
|
||||||
loading,
|
loading,
|
||||||
fetchMatches,
|
|
||||||
refreshMatches,
|
|
||||||
updateState,
|
updateState,
|
||||||
filterCount,
|
filterCount,
|
||||||
updateFilterOptions, // 更新筛选条件
|
updateFilterOptions, // 更新筛选条件
|
||||||
@@ -26,14 +26,29 @@ const SearchResult = () => {
|
|||||||
clearFilterOptions,
|
clearFilterOptions,
|
||||||
distanceData,
|
distanceData,
|
||||||
quickFilterData,
|
quickFilterData,
|
||||||
} = useSearchResultState() || {};
|
loadMoreMatches,
|
||||||
|
getMatchesData
|
||||||
|
} = useListStore() || {};
|
||||||
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
||||||
const { totalHeight } = statusNavbarHeightInfo || {};
|
const { totalHeight } = statusNavbarHeightInfo || {};
|
||||||
const isSelect = filterCount > 0;
|
const isSelect = filterCount > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const pages = Taro.getCurrentPages()
|
||||||
|
const currentPage = pages?.[pages.length - 1];
|
||||||
|
updateState({currentPage, isSearchResult: true})
|
||||||
|
// if (location?.address) {
|
||||||
|
// 保存位置
|
||||||
|
// updateState({ location });
|
||||||
// 页面加载时获取数据
|
// 页面加载时获取数据
|
||||||
fetchMatches();
|
console.log('===搜索结果页===')
|
||||||
|
getMatchesData();
|
||||||
|
// }
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.log('===搜索结果组件卸载')
|
||||||
|
updateState({currentPage: '', isSearchResult: false})
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,14 +73,30 @@ const SearchResult = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refreshMatches = () => {
|
||||||
|
getMatchesData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeftIconClick = () => {
|
||||||
|
Taro.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="searchResultPage">
|
<View className="searchResultPage">
|
||||||
|
{/* 自定义导航 */}
|
||||||
|
<CustomerNavBar
|
||||||
|
config={{
|
||||||
|
showInput: true,
|
||||||
|
inputLeftIcon: img.ICON_LIST_SEARCH_BACK,
|
||||||
|
leftIconClick: handleLeftIconClick
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/* 筛选 */}
|
{/* 筛选 */}
|
||||||
<View
|
<View
|
||||||
className="searchResultFilterWrapper"
|
className="searchResultFilterWrapper"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
// top: `${totalHeight}px`
|
top: `${totalHeight}px`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -79,10 +110,13 @@ const SearchResult = () => {
|
|||||||
quickValue={distanceQuickFilter?.quick}
|
quickValue={distanceQuickFilter?.quick}
|
||||||
/>
|
/>
|
||||||
{/* 筛选 icon */}
|
{/* 筛选 icon */}
|
||||||
<View className={`filterIconWrapper ${isSelect && 'active'}`} onClick={toggleShowPopup}>
|
<View
|
||||||
|
className={`filterIconWrapper ${isSelect && "active"}`}
|
||||||
|
onClick={toggleShowPopup}
|
||||||
|
>
|
||||||
<Image
|
<Image
|
||||||
src={isSelect ? img.ICON_FILTER_SELECTED : img.ICON_FILTER}
|
src={isSelect ? img.ICON_FILTER_SELECTED : img.ICON_FILTER}
|
||||||
className={`filterIcon ${isSelect && 'active'}`}
|
className={`filterIcon ${isSelect && "active"}`}
|
||||||
/>
|
/>
|
||||||
{isSelect && <Text className="filterCount">{filterCount}</Text>}
|
{isSelect && <Text className="filterCount">{filterCount}</Text>}
|
||||||
</View>
|
</View>
|
||||||
@@ -99,7 +133,8 @@ const SearchResult = () => {
|
|||||||
onClear={clearFilterOptions}
|
onClear={clearFilterOptions}
|
||||||
visible={isShowFilterPopup}
|
visible={isShowFilterPopup}
|
||||||
onClose={toggleShowPopup}
|
onClose={toggleShowPopup}
|
||||||
statusNavbarHeigh={0}
|
// statusNavbarHeigh={0}
|
||||||
|
statusNavbarHeigh={statusNavbarHeightInfo?.totalHeight}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -107,11 +142,12 @@ const SearchResult = () => {
|
|||||||
|
|
||||||
{/* 列表内容 */}
|
{/* 列表内容 */}
|
||||||
<ListContainer
|
<ListContainer
|
||||||
data={matches}
|
data={searchResultData}
|
||||||
recommendList={recommendList}
|
recommendList={recommendList}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
reload={refreshMatches}
|
reload={refreshMatches}
|
||||||
|
loadMoreMatches={loadMoreMatches}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,35 +16,36 @@ interface ApiResponse<T> {
|
|||||||
* @param params 查询参数
|
* @param params 查询参数
|
||||||
* @returns Promise<TennisMatch[]>
|
* @returns Promise<TennisMatch[]>
|
||||||
*/
|
*/
|
||||||
export const getGamesList = async (params?: {
|
export const getGamesList = async (params?: Record<string, any>) => {
|
||||||
page?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
location?: string;
|
|
||||||
skillLevel?: string;
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
return httpService.post('/games/list', params, { showLoading: false })
|
/**
|
||||||
|
* 判断请求哪个接口
|
||||||
|
* 筛选智能排序:/games/integrate_list
|
||||||
|
* 常规搜索:/games/list
|
||||||
|
*/
|
||||||
|
// const isIntegrate = params?.order === '0';
|
||||||
|
// const fetchApi = isIntegrate ? '/games/integrate_list' : '/games/list'
|
||||||
|
return httpService.post('/games/list', params, { showLoading: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("列表数据获取失败:", error);
|
console.error("列表数据获取失败:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGamesIntegrateList = async (params?: {
|
/**
|
||||||
page?: number;
|
* 获取列表
|
||||||
pageSize?: number;
|
* @param params 查询参数
|
||||||
location?: string;
|
* @returns Promise<TennisMatch[]>
|
||||||
skillLevel?: string;
|
*/
|
||||||
}) => {
|
export const getGamesIntegrateList = async (params?: Record<string, any>) => {
|
||||||
try {
|
try {
|
||||||
return httpService.post('/games/integrate_list', params, { showLoading: false })
|
return httpService.post('/games/integrate_list', params, { showLoading: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("列表数据获取失败:", error);
|
console.error("列表数据获取失败:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取搜索历史记录的异步函数
|
* 获取搜索历史记录的异步函数
|
||||||
* @param {Object} params - 查询参数对象
|
* @param {Object} params - 查询参数对象
|
||||||
@@ -53,7 +54,7 @@ export const getGamesIntegrateList = async (params?: {
|
|||||||
export const getSearchHistory = async (params) => {
|
export const getSearchHistory = async (params) => {
|
||||||
try {
|
try {
|
||||||
// 调用HTTP服务获取搜索历史记录
|
// 调用HTTP服务获取搜索历史记录
|
||||||
return httpService.get('/games/search_history', params)
|
return httpService.post('/games/search_history', params, { showLoading: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 捕获并打印错误信息
|
// 捕获并打印错误信息
|
||||||
console.error("历史记录获取失败:", error);
|
console.error("历史记录获取失败:", error);
|
||||||
@@ -66,10 +67,10 @@ export const getSearchHistory = async (params) => {
|
|||||||
* @description 清除搜索历史
|
* @description 清除搜索历史
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const clearHistory = async () => {
|
export const clearHistory = async (params) => {
|
||||||
try {
|
try {
|
||||||
// 调用HTTP服务清除搜索历史记录
|
// 调用HTTP服务清除搜索历史记录
|
||||||
return httpService.post('/search_history/delete_all')
|
return httpService.post('/games/search_history/delete_all', params, { showLoading: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 捕获并打印错误信息
|
// 捕获并打印错误信息
|
||||||
console.error("清除历史记录失败:", error);
|
console.error("清除历史记录失败:", error);
|
||||||
@@ -86,7 +87,7 @@ export const clearHistory = async () => {
|
|||||||
export const searchSuggestion = async (params) => {
|
export const searchSuggestion = async (params) => {
|
||||||
try {
|
try {
|
||||||
// 调用HTTP服务获取搜索建议
|
// 调用HTTP服务获取搜索建议
|
||||||
return httpService.get('/games/search_recommendations', params)
|
return httpService.post('/games/search_recommendations', params)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 捕获并打印错误信息
|
// 捕获并打印错误信息
|
||||||
console.error("搜索建议获取失败:", error);
|
console.error("搜索建议获取失败:", error);
|
||||||
|
|||||||
@@ -1,38 +1,40 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from "zustand";
|
||||||
import { getNavbarHeight } from '@/utils/getNavbarHeight'
|
import { getNavbarHeight } from "@/utils/getNavbarHeight";
|
||||||
|
import { getCurrentLocation } from "@/utils/locationUtils";
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
location: Record<string, any>
|
location: Record<string, any>;
|
||||||
getLocationLoading: boolean
|
getLocationLoading: boolean;
|
||||||
getLocationText: string
|
getLocationText: string;
|
||||||
statusNavbarHeightInfo: {
|
statusNavbarHeightInfo: {
|
||||||
statusBarHeight: number
|
statusBarHeight: number;
|
||||||
navbarHeight: number
|
navbarHeight: number;
|
||||||
totalHeight: number
|
totalHeight: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GlobalActions {
|
interface GlobalActions {
|
||||||
updateState: (payload: Record<string, any>) => void
|
updateState: (payload: Record<string, any>) => void;
|
||||||
getNavbarHeightInfo: () => void
|
getNavbarHeightInfo: () => void;
|
||||||
|
getCurrentLocationInfo: () => void;
|
||||||
}
|
}
|
||||||
// 完整的 Store 类型
|
// 完整的 Store 类型
|
||||||
type GlobalStore = GlobalState & GlobalActions
|
type GlobalStore = GlobalState & GlobalActions;
|
||||||
|
|
||||||
// 创建 store
|
// 创建 store
|
||||||
export const useGlobalStore = create<GlobalStore>()((set, get) => ({
|
export const useGlobalStore = create<GlobalStore>()((set, get) => ({
|
||||||
// 位置信息
|
// 位置信息
|
||||||
location: {},
|
location: {},
|
||||||
// 正在获取位置信息
|
// 正在获取位置信息
|
||||||
getLocationLoading: false,
|
getLocationLoading: true,
|
||||||
// 获取位置信息文本
|
// 获取位置信息文本
|
||||||
getLocationText: '定位中...',
|
getLocationText: "定位中...",
|
||||||
|
|
||||||
// 状态栏和导航栏高度信息
|
// 状态栏和导航栏高度信息
|
||||||
statusNavbarHeightInfo: {
|
statusNavbarHeightInfo: {
|
||||||
statusBarHeight: 0,
|
statusBarHeight: 0,
|
||||||
navbarHeight: 0,
|
navbarHeight: 0,
|
||||||
totalHeight: 0
|
totalHeight: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取导航栏高度信息
|
// 获取导航栏高度信息
|
||||||
@@ -42,9 +44,19 @@ export const useGlobalStore = create<GlobalStore>()((set, get) => ({
|
|||||||
statusNavbarHeightInfo: {
|
statusNavbarHeightInfo: {
|
||||||
statusBarHeight,
|
statusBarHeight,
|
||||||
navbarHeight,
|
navbarHeight,
|
||||||
totalHeight: statusBarHeight + navbarHeight
|
totalHeight: statusBarHeight + navbarHeight,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取位置信息
|
||||||
|
getCurrentLocationInfo: () => {
|
||||||
|
getCurrentLocation().then((res) => {
|
||||||
|
set({
|
||||||
|
getLocationLoading: false,
|
||||||
|
location: res || {},
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新store数据
|
// 更新store数据
|
||||||
@@ -52,10 +64,10 @@ export const useGlobalStore = create<GlobalStore>()((set, get) => ({
|
|||||||
const state = get();
|
const state = get();
|
||||||
set({
|
set({
|
||||||
...state,
|
...state,
|
||||||
...(payload || {})
|
...(payload || {}),
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// 导出便捷的 hooks
|
// 导出便捷的 hooks
|
||||||
export const useGlobalState = () => useGlobalStore((state) => state)
|
export const useGlobalState = () => useGlobalStore((state) => state);
|
||||||
|
|||||||
@@ -1,22 +1,49 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from "zustand";
|
||||||
import { getGamesList, getGamesIntegrateList, getSearchHistory, clearHistory, searchSuggestion } from '../services/listApi'
|
import {
|
||||||
import { ListActions, IFilterOptions, ListState } from '../../types/list/types'
|
getGamesList,
|
||||||
|
getGamesIntegrateList,
|
||||||
|
getSearchHistory,
|
||||||
|
clearHistory,
|
||||||
|
searchSuggestion,
|
||||||
|
} from "../services/listApi";
|
||||||
|
import {
|
||||||
|
ListActions,
|
||||||
|
IFilterOptions,
|
||||||
|
ListState,
|
||||||
|
IPayload,
|
||||||
|
} from "../../types/list/types";
|
||||||
|
|
||||||
|
import dateRangeUtils from '@/utils/dateRange'
|
||||||
|
|
||||||
// 完整的 Store 类型
|
// 完整的 Store 类型
|
||||||
type TennisStore = ListState & ListActions
|
type TennisStore = ListState & ListActions;
|
||||||
|
|
||||||
const defaultFilterOptions: IFilterOptions = {
|
const defaultFilterOptions: IFilterOptions = {
|
||||||
dateRange: [], // 日期区间
|
dateRange: [new Date(), new Date()], // 日期区间
|
||||||
timeSlot: '', // 时间段
|
dateRangeQuick: '',
|
||||||
|
timeSlot: "", // 时间段
|
||||||
ntrp: [1, 5], // NTRP 水平区间
|
ntrp: [1, 5], // NTRP 水平区间
|
||||||
venueType: '', // 场地类型
|
venueType: "", // 场地类型
|
||||||
playType: '', // 玩法
|
playType: "", // 玩法
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultDistance = 'all'; // 默认距离
|
// const defaultDistance = "all"; // 默认距离
|
||||||
|
const defaultDistanceQuickFilter = {
|
||||||
|
distance: "",
|
||||||
|
quick: "0",
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultPageOption = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
};
|
||||||
|
|
||||||
// 创建 store
|
// 创建 store
|
||||||
export const useListStore = create<TennisStore>()((set, get) => ({
|
export const useListStore = create<TennisStore>()((set, get) => ({
|
||||||
|
currentPage: "",
|
||||||
|
isSearchResult: false,
|
||||||
|
searchResultData: [],
|
||||||
|
// dateRange: [new Date(), new Date()], // 日期区间
|
||||||
// 初始状态
|
// 初始状态
|
||||||
matches: [],
|
matches: [],
|
||||||
// 推荐列表
|
// 推荐列表
|
||||||
@@ -26,10 +53,10 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
longitude: 0,
|
longitude: 0,
|
||||||
}, // 位置
|
}, // 位置
|
||||||
// 是否加载中
|
// 是否加载中
|
||||||
loading: false,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
// 搜索的value
|
// 搜索的value
|
||||||
searchValue: '',
|
searchValue: "",
|
||||||
// 是否展示综合筛选弹窗
|
// 是否展示综合筛选弹窗
|
||||||
isShowFilterPopup: false,
|
isShowFilterPopup: false,
|
||||||
// 综合筛选项
|
// 综合筛选项
|
||||||
@@ -37,56 +64,46 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
// 综合筛选 选择的筛选数量
|
// 综合筛选 选择的筛选数量
|
||||||
filterCount: 0,
|
filterCount: 0,
|
||||||
// 距离筛选
|
// 距离筛选
|
||||||
distance: defaultDistance,
|
// distance: defaultDistance,
|
||||||
// 快捷筛选
|
// 快捷筛选
|
||||||
quickFilter: 1, // 1: 默认 2: 好评 3: 销量
|
quickFilter: 1, // 1: 默认 2: 好评 3: 销量
|
||||||
// 距离筛选数据
|
// 距离筛选数据
|
||||||
distanceData: [
|
distanceData: [
|
||||||
{ id: 0, label: "全城", value: "0" },
|
{ id: 0, label: "全城", value: "" },
|
||||||
{ id: 1, label: "3km", value: "3" },
|
{ id: 1, label: "3km", value: "3" },
|
||||||
{ id: 2, label: "5km", value: "5" },
|
{ id: 2, label: "5km", value: "5" },
|
||||||
{ id: 3, label: "10km", value: "10" },
|
{ id: 3, label: "10km", value: "10" },
|
||||||
],
|
],
|
||||||
// 快捷筛选数据
|
// 快捷筛选数据
|
||||||
quickFilterData: [
|
quickFilterData: [
|
||||||
{ text: "默认排序", value: "0" },
|
{ label: "智能排序", value: "0" },
|
||||||
{ text: "好评排序", value: "1" },
|
{ label: "距离更近", value: "1" },
|
||||||
{ text: "销量排序", value: "2" },
|
{ label: "时间更近", value: "2" },
|
||||||
],
|
],
|
||||||
// 距离筛选和快捷筛选
|
// 距离筛选和快捷筛选
|
||||||
distanceQuickFilter: {
|
distanceQuickFilter: defaultDistanceQuickFilter,
|
||||||
distance: '全城',
|
|
||||||
quick: '0',
|
// 气泡日期范围
|
||||||
},
|
dateRangeOptions: [
|
||||||
|
{ id: 1, label: "本周末", value: "1" },
|
||||||
|
{ id: 2, label: "一周内", value: "2" },
|
||||||
|
{ id: 3, label: "一月内", value: "3" },
|
||||||
|
],
|
||||||
// 时间气泡数据
|
// 时间气泡数据
|
||||||
timeBubbleData: [
|
timeBubbleData: [
|
||||||
{ id: 1, label: "晨间 6:00-10:00", value: "1" },
|
{ id: 1, label: "晨间 6:00-10:00", value: "6:00-10:00" },
|
||||||
{ id: 2, label: "上午 10:00-12:00", value: "2" },
|
{ id: 2, label: "上午 10:00-12:00", value: "10:00-12:00" },
|
||||||
{ id: 3, label: "中午 12:00-14:00", value: "3" },
|
{ id: 3, label: "中午 12:00-14:00", value: "12:00-14:00" },
|
||||||
{ id: 4, label: "下午 14:00-18:00", value: "4" },
|
{ id: 4, label: "下午 14:00-18:00", value: "14:00-18:00" },
|
||||||
{ id: 5, label: "晚上 18:00-22:00", value: "5" },
|
{ id: 5, label: "晚上 18:00-22:00", value: "18:00-22:00" },
|
||||||
{ id: 6, label: "夜间 22:00-24:00", value: "6" },
|
{ id: 6, label: "夜间 22:00-24:00", value: "22:00-24:00" },
|
||||||
],
|
|
||||||
// 场地类型数据
|
|
||||||
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,
|
gamesNum: 0,
|
||||||
// 页面滚动距离顶部距离 是否大于0
|
// 页面滚动距离顶部距离 是否大于0
|
||||||
isScrollTop: false,
|
isScrollTop: false,
|
||||||
// 搜索历史数据
|
// 搜索历史数据
|
||||||
searchHistory: ['上海', '黄浦', '上海', '静安', '徐汇', '黄浦', '普陀', '黄浦', '长宁', '黄浦'],
|
searchHistory: [],
|
||||||
// 搜索历史数据默认 Top 15
|
// 搜索历史数据默认 Top 15
|
||||||
searchHistoryParams: {
|
searchHistoryParams: {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -105,157 +122,249 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
// 打开快捷筛选框
|
// 打开快捷筛选框
|
||||||
isOpenQuickFilterPopup: false,
|
isOpenQuickFilterPopup: false,
|
||||||
// 分页
|
// 分页
|
||||||
pageOption: {
|
pageOption: defaultPageOption,
|
||||||
page: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 组装搜索数据
|
// 组装搜索数据
|
||||||
getSearchParams: () => {
|
getSearchParams: () => {
|
||||||
const state = get()
|
const state = get();
|
||||||
const filterOptions = state?.filterOptions || {};
|
const filterOptions = state?.filterOptions || {};
|
||||||
|
console.log('===1111filterOptions', filterOptions)
|
||||||
|
const distanceQuickFilter = state?.distanceQuickFilter || {};
|
||||||
const params = {
|
const params = {
|
||||||
pageOption: state.pageOption,
|
pageOption: state.pageOption,
|
||||||
seachOption: {
|
seachOption: {
|
||||||
...filterOptions,
|
...filterOptions,
|
||||||
title: state.searchValue,
|
title: state.searchValue,
|
||||||
|
ntrpMin: filterOptions?.ntrp?.[0],
|
||||||
|
ntrpMax: filterOptions?.ntrp?.[1],
|
||||||
|
dateRange: [
|
||||||
|
dateRangeUtils.formatDate(filterOptions?.dateRange?.[0]),
|
||||||
|
dateRangeUtils.formatDate(filterOptions?.dateRange?.[1])
|
||||||
|
],
|
||||||
},
|
},
|
||||||
order: '',
|
order: distanceQuickFilter?.quick,
|
||||||
lat: state?.location?.latitude,
|
lat: state?.location?.latitude,
|
||||||
lng: state?.location?.longitude,
|
lng: state?.location?.longitude,
|
||||||
}
|
};
|
||||||
|
console.log('===列表参数params', params)
|
||||||
return params;
|
return params;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 初始化获取比赛数据
|
// 设置搜索的列表结果
|
||||||
|
setListData: (payload: IPayload) => {
|
||||||
|
const { isSearchResult } = get();
|
||||||
|
const { error, data, loading, gamesNum } = payload;
|
||||||
|
const saveKey = isSearchResult ? "searchResultData" : "matches";
|
||||||
|
const saveData = { error, loading, gamesNum, [saveKey]: data };
|
||||||
|
set(saveData);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取列表数据(常规搜索)
|
||||||
fetchMatches: async (params) => {
|
fetchMatches: async (params) => {
|
||||||
set({ loading: true, error: null })
|
set({ loading: true, error: null });
|
||||||
|
const { getSearchParams, setListData, distanceQuickFilter } = get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { getSearchParams } = get();
|
|
||||||
const searchParams = getSearchParams() || {};
|
const searchParams = getSearchParams() || {};
|
||||||
const reqParams = {
|
const reqParams = {
|
||||||
...(searchParams || {}),
|
...(searchParams || {}),
|
||||||
...params,
|
...params,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isIntegrate = distanceQuickFilter?.quick === "0";
|
||||||
|
let fetchFn = getGamesList;
|
||||||
|
|
||||||
|
if (isIntegrate) {
|
||||||
|
reqParams.order = "";
|
||||||
|
fetchFn = getGamesIntegrateList;
|
||||||
}
|
}
|
||||||
const resData = await getGamesList(reqParams) || {};
|
|
||||||
console.log('===resData', resData)
|
console.log("===fetchMatches 获取列表数据参数:", reqParams);
|
||||||
|
const resData = (await fetchFn(reqParams)) || {};
|
||||||
const { data = {}, code } = resData;
|
const { data = {}, code } = resData;
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
set({
|
setListData({
|
||||||
error: '1',
|
error: "-1",
|
||||||
matches: [],
|
data: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
})
|
gamesNum: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const { count, rows } = data;
|
const { count, rows } = data;
|
||||||
// const list = (rows || []).map(() => {
|
setListData({
|
||||||
// return {
|
// recommendList: rows || [],
|
||||||
// id: "3",
|
error: '',
|
||||||
// title: "黄浦区双打约球",
|
data: rows || [],
|
||||||
// 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 || [],
|
|
||||||
recommendList: rows || [],
|
|
||||||
error: null,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
gamesNum: count,
|
gamesNum: count,
|
||||||
})
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
set({
|
setListData({
|
||||||
error,
|
error: "-1",
|
||||||
matches: [],
|
data: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
})
|
gamesNum: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 获取列表数据(智能筛选)
|
||||||
|
// 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: () => {
|
||||||
|
const { fetchMatches } = get();
|
||||||
|
fetchMatches();
|
||||||
|
// if (distanceQuickFilter?.quick === "0") {
|
||||||
|
// getIntegrateListData();
|
||||||
|
// } else {
|
||||||
|
// fetchMatches();
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
|
||||||
// 获取历史搜索数据
|
// 获取历史搜索数据
|
||||||
getSearchHistory: async () => {
|
getSearchHistory: async () => {
|
||||||
try {
|
try {
|
||||||
const params = get()?.searchHistoryParams || {}
|
const params = get()?.searchHistoryParams || {};
|
||||||
const resData = await getSearchHistory(params) || {};
|
const resData = (await getSearchHistory(params)) || {};
|
||||||
console.log('===resData', resData)
|
const searchHistory = resData?.data?.records || [];
|
||||||
} catch (error) {
|
set({
|
||||||
|
searchHistory,
|
||||||
}
|
});
|
||||||
|
} catch (error) { }
|
||||||
},
|
},
|
||||||
|
|
||||||
// 清空历史记录
|
// 清空历史记录
|
||||||
clearHistory: async () => {
|
clearHistory: async () => {
|
||||||
try {
|
try {
|
||||||
const resData = await clearHistory() || {};
|
const params = {};
|
||||||
} catch (error) {
|
const resData = (await clearHistory(params)) || {};
|
||||||
|
if (resData?.code === 0) {
|
||||||
}
|
|
||||||
set({
|
set({
|
||||||
searchHistory: [],
|
searchHistory: [],
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取联想
|
// 获取联想
|
||||||
searchSuggestion: async (val: string) => {
|
searchSuggestion: async (val: string) => {
|
||||||
try {
|
try {
|
||||||
const resData = await searchSuggestion({ val }) || {};
|
const resData = (await searchSuggestion({ val, limit: 10 })) || {};
|
||||||
console.log('===获取联想', resData)
|
const recommendations = resData?.data?.recommendations || [];
|
||||||
// set({
|
const total = resData?.data?.total;
|
||||||
// suggestionList: ['上海球局', '黄浦球局', '上海球局', '静安球局', '徐汇球局', '黄浦球局', '普陀球局', '黄浦球局', '长宁球局', '黄浦球局'],
|
set({
|
||||||
// isShowSuggestion: true,
|
suggestionList: recommendations,
|
||||||
// })
|
isShowSuggestion: total > 0,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
set({
|
set({
|
||||||
suggestionList: ['上海球局', '黄浦球局', '上海球局', '静安球局', '徐汇球局', '黄浦球局', '普陀球局', '黄浦球局', '长宁球局', '黄浦球局'],
|
suggestionList: [],
|
||||||
isShowSuggestion: true,
|
isShowSuggestion: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 清除错误信息
|
// 清除错误信息
|
||||||
clearError: () => {
|
clearError: () => {
|
||||||
set({ error: null })
|
set({ error: null });
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新综合筛选项
|
// 更新综合筛选项
|
||||||
updateFilterOptions: (payload: Record<string, any>) => {
|
updateFilterOptions: (payload: Record<string, any>) => {
|
||||||
const preFilterOptions = get()?.filterOptions || {}
|
const { filterOptions: preFilterOptions, getMatchesData } = get() || {};
|
||||||
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)
|
console.log("===更新综合筛选项", filterOptions, filterCount);
|
||||||
set({
|
set({
|
||||||
filterOptions,
|
filterOptions,
|
||||||
filterCount
|
filterCount,
|
||||||
})
|
pageOption: defaultPageOption,
|
||||||
|
});
|
||||||
|
// 重新搜索数据
|
||||||
|
getMatchesData();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 清空综合筛选选项
|
// 清空综合筛选选项
|
||||||
clearFilterOptions: () => {
|
clearFilterOptions: () => {
|
||||||
|
const { getMatchesData } = get() || {};
|
||||||
set({
|
set({
|
||||||
filterOptions: defaultFilterOptions,
|
filterOptions: defaultFilterOptions,
|
||||||
filterCount: 0
|
filterCount: 0,
|
||||||
})
|
pageOption: defaultPageOption,
|
||||||
|
});
|
||||||
|
getMatchesData();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载更多数据
|
||||||
|
loadMoreMatches: () => {
|
||||||
|
const { pageOption, getMatchesData } = get() || {};
|
||||||
|
set({
|
||||||
|
pageOption: {
|
||||||
|
page: pageOption?.page + 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
getMatchesData();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 初始化搜索条件 重新搜索
|
||||||
|
initialFilterSearch: () => {
|
||||||
|
const { getMatchesData } = get();
|
||||||
|
set({
|
||||||
|
distanceQuickFilter: defaultDistanceQuickFilter,
|
||||||
|
filterOptions: defaultFilterOptions,
|
||||||
|
pageOption: defaultPageOption,
|
||||||
|
});
|
||||||
|
getMatchesData();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 更新store数据
|
// 更新store数据
|
||||||
updateState: (payload: Record<string, any>) => {
|
updateState: (payload: Record<string, any>) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
console.log('Store: 更新数据:', state);
|
console.log("Store: 更新数据:", state);
|
||||||
set({
|
set({
|
||||||
...(payload || {})
|
...(payload || {}),
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// 导出便捷的 hooks
|
// 导出便捷的 hooks
|
||||||
export const useListState = () => useListStore((state) => state)
|
export const useListState = () => useListStore((state) => state);
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
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)
|
|
||||||
133
src/utils/dateRange.ts
Normal file
133
src/utils/dateRange.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* 日期范围计算工具函数
|
||||||
|
* 提供从当前日期开始计算本周末、一周内和一个月内的开始和结束日期
|
||||||
|
*/
|
||||||
|
interface DateRange {
|
||||||
|
start: Date;
|
||||||
|
end: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormattedDateRange {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AllFormattedRanges {
|
||||||
|
thisWeekend: FormattedDateRange;
|
||||||
|
nextWeek: FormattedDateRange;
|
||||||
|
nextMonth: FormattedDateRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前日期
|
||||||
|
* @returns {Date} 当前日期对象
|
||||||
|
*/
|
||||||
|
const getCurrentDate = (): Date => {
|
||||||
|
return new Date();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算本周末(当前周的周六和周日)
|
||||||
|
* @param {Date} [baseDate] 基准日期,默认为当前日期
|
||||||
|
* @returns {DateRange} 包含start和end的日期对象
|
||||||
|
*/
|
||||||
|
const getThisWeekend = (baseDate?: Date): DateRange => {
|
||||||
|
const current = baseDate ? new Date(baseDate) : getCurrentDate();
|
||||||
|
// 计算到周六的天数(getDay()返回0-6,0是周日,6是周六)
|
||||||
|
const daysToSaturday = 6 - current.getDay();
|
||||||
|
|
||||||
|
// 计算周六日期
|
||||||
|
const saturday = new Date(current);
|
||||||
|
saturday.setDate(current.getDate() + daysToSaturday);
|
||||||
|
|
||||||
|
// 计算周日日期
|
||||||
|
const sunday = new Date(saturday);
|
||||||
|
sunday.setDate(saturday.getDate() + 1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: saturday,
|
||||||
|
end: sunday
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算一周内(从基准日期开始的7天)
|
||||||
|
* @param {Date} [baseDate] 基准日期,默认为当前日期
|
||||||
|
* @returns {DateRange} 包含start和end的日期对象
|
||||||
|
*/
|
||||||
|
const getNextWeekRange = (baseDate?: Date): DateRange => {
|
||||||
|
const start = baseDate ? new Date(baseDate) : getCurrentDate();
|
||||||
|
const end = new Date(start);
|
||||||
|
end.setDate(start.getDate() + 6); // 今天 + 6天 = 7天后
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算一个月内(从基准日期开始的30天)
|
||||||
|
* @param {Date} [baseDate] 基准日期,默认为当前日期
|
||||||
|
* @returns {DateRange} 包含start和end的日期对象
|
||||||
|
*/
|
||||||
|
const getNextMonthRange = (baseDate?: Date): DateRange => {
|
||||||
|
const start = baseDate ? new Date(baseDate) : getCurrentDate();
|
||||||
|
const end = new Date(start);
|
||||||
|
end.setDate(start.getDate() + 29); // 今天 + 29天 = 30天后
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期为 YYYY-MM-DD 格式
|
||||||
|
* @param {Date} date 日期对象
|
||||||
|
* @returns {string} 格式化后的日期字符串
|
||||||
|
* @throws {Error} 如果传入的不是有效的日期对象则抛出错误
|
||||||
|
*/
|
||||||
|
const formatDate = (date: Date): string => {
|
||||||
|
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||||
|
throw new Error('Invalid date object: 请传入有效的Date实例');
|
||||||
|
}
|
||||||
|
return date.toISOString().split('T')[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有日期范围的格式化结果
|
||||||
|
* @param {Date} [baseDate] 基准日期,默认为当前日期
|
||||||
|
* @returns {AllFormattedRanges} 包含所有格式化后的日期范围
|
||||||
|
*/
|
||||||
|
const getAllFormattedRanges = (baseDate?: Date): AllFormattedRanges => {
|
||||||
|
const weekend = getThisWeekend(baseDate);
|
||||||
|
const week = getNextWeekRange(baseDate);
|
||||||
|
const month = getNextMonthRange(baseDate);
|
||||||
|
|
||||||
|
return {
|
||||||
|
thisWeekend: {
|
||||||
|
start: formatDate(weekend.start),
|
||||||
|
end: formatDate(weekend.end)
|
||||||
|
},
|
||||||
|
nextWeek: {
|
||||||
|
start: formatDate(week.start),
|
||||||
|
end: formatDate(week.end)
|
||||||
|
},
|
||||||
|
nextMonth: {
|
||||||
|
start: formatDate(month.start),
|
||||||
|
end: formatDate(month.end)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dateRangeUtils = {
|
||||||
|
getCurrentDate,
|
||||||
|
getThisWeekend,
|
||||||
|
getNextWeekRange,
|
||||||
|
getNextMonthRange,
|
||||||
|
formatDate,
|
||||||
|
getAllFormattedRanges
|
||||||
|
};
|
||||||
|
|
||||||
|
export default dateRangeUtils;
|
||||||
@@ -20,6 +20,7 @@ export const getCurrentLocation = (): Promise<LocationInfo> => {
|
|||||||
Taro.getLocation({
|
Taro.getLocation({
|
||||||
type: 'wgs84',
|
type: 'wgs84',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
console.log('===获取地理位置', res)
|
||||||
// 使用逆地理编码获取地址信息
|
// 使用逆地理编码获取地址信息
|
||||||
reverseGeocode(res.latitude, res.longitude)
|
reverseGeocode(res.latitude, res.longitude)
|
||||||
.then(address => {
|
.then(address => {
|
||||||
|
|||||||
@@ -13,13 +13,18 @@ export interface TennisMatch {
|
|||||||
shinei: string
|
shinei: string
|
||||||
}
|
}
|
||||||
export interface IFilterOptions {
|
export interface IFilterOptions {
|
||||||
dateRange: [], // 日期区间
|
dateRange: [Date, Date], // 日期区间
|
||||||
|
dateRangeQuick?: string, // 日期快捷选择
|
||||||
timeSlot?: string, // 时间段
|
timeSlot?: string, // 时间段
|
||||||
ntrp?: [number, number], // NTRP 水平区间
|
ntrp?: [number, number], // NTRP 水平区间
|
||||||
venueType?: string, // 场地类型
|
venueType?: string, // 场地类型
|
||||||
playType?: string, // 玩法
|
playType?: string, // 玩法
|
||||||
|
distanceFilter?: string
|
||||||
}
|
}
|
||||||
export interface ListState {
|
export interface ListState {
|
||||||
|
currentPage: string
|
||||||
|
isSearchResult: boolean
|
||||||
|
searchResultData: TennisMatch[]
|
||||||
matches: TennisMatch[]
|
matches: TennisMatch[]
|
||||||
recommendList: TennisMatch[]
|
recommendList: TennisMatch[]
|
||||||
location: {
|
location: {
|
||||||
@@ -32,7 +37,7 @@ export interface ListState {
|
|||||||
isShowFilterPopup: boolean
|
isShowFilterPopup: boolean
|
||||||
filterOptions: IFilterOptions
|
filterOptions: IFilterOptions
|
||||||
filterCount: number
|
filterCount: number
|
||||||
distance: string | number
|
// distance: string | number
|
||||||
quickFilter: string | number
|
quickFilter: string | number
|
||||||
distanceData: any[]
|
distanceData: any[]
|
||||||
quickFilterData: any[]
|
quickFilterData: any[]
|
||||||
@@ -41,12 +46,11 @@ export interface ListState {
|
|||||||
quick: string
|
quick: string
|
||||||
}
|
}
|
||||||
timeBubbleData: BubbleOption[]
|
timeBubbleData: BubbleOption[]
|
||||||
locationOptions: BubbleOption[]
|
dateRangeOptions: BubbleOption[]
|
||||||
gamePlayOptions: BubbleOption[]
|
|
||||||
gamesNum: number
|
gamesNum: number
|
||||||
isScrollTop: boolean
|
isScrollTop: boolean
|
||||||
searchHistoryParams: Record<string, any>
|
searchHistoryParams: Record<string, any>
|
||||||
searchHistory: string[]
|
searchHistory: {id: number, title: string}[]
|
||||||
suggestionList: string[]
|
suggestionList: string[]
|
||||||
isShowSuggestion: boolean
|
isShowSuggestion: boolean
|
||||||
isShowInputCustomerNavBar: boolean
|
isShowInputCustomerNavBar: boolean
|
||||||
@@ -60,13 +64,9 @@ export interface ListState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ListActions {
|
export interface ListActions {
|
||||||
fetchMatches: (params?: {
|
fetchMatches: (params?: Record<string, any>) => Promise<void>
|
||||||
page?: number
|
// getIntegrateListData: (params?: Record<string, any>) => Promise<void>
|
||||||
pageSize?: number
|
getMatchesData: () => void
|
||||||
location?: string
|
|
||||||
skillLevel?: string
|
|
||||||
}) => 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
|
||||||
@@ -75,6 +75,16 @@ export interface ListActions {
|
|||||||
clearHistory: () => void
|
clearHistory: () => void
|
||||||
searchSuggestion: (val: string) => Promise<void>
|
searchSuggestion: (val: string) => Promise<void>
|
||||||
getSearchParams: () => Record<string, any>
|
getSearchParams: () => Record<string, any>
|
||||||
|
loadMoreMatches: () => void
|
||||||
|
initialFilterSearch: () => void
|
||||||
|
setListData: (payload: IPayload) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPayload {
|
||||||
|
error: string;
|
||||||
|
data: TennisMatch[];
|
||||||
|
loading: boolean;
|
||||||
|
gamesNum: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 快捷筛选
|
// 快捷筛选
|
||||||
@@ -143,27 +153,32 @@ export interface BubbleItemProps {
|
|||||||
export interface FilterPopupProps {
|
export interface FilterPopupProps {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onChange: (params: Record<string, string>) => void;
|
onChange: (params: Record<string, any>) => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
filterOptions: Record<string, any>;
|
filterOptions: Record<string, any>;
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
statusNavbarHeigh: number
|
statusNavbarHeigh: number
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 列表卡片
|
// 列表卡片
|
||||||
export interface ListCardProps {
|
export interface ListCardProps {
|
||||||
id: number;
|
id: string | number,
|
||||||
title: string;
|
title: string,
|
||||||
dateTime: string;
|
start_time: string,
|
||||||
location: string;
|
location: string,
|
||||||
distance: string;
|
distance_km: number,
|
||||||
registeredCount: number;
|
current_players: number,
|
||||||
maxCount: number;
|
max_players: number,
|
||||||
skillLevel: string;
|
skill_level_min: number,
|
||||||
|
skill_level_max: number,
|
||||||
|
play_type: string,
|
||||||
|
image_list: string[],
|
||||||
|
court_type: string,
|
||||||
matchType: string;
|
matchType: string;
|
||||||
images: string[];
|
|
||||||
shinei: string;
|
shinei: string;
|
||||||
showSkeleton?: boolean;
|
showSkeleton?: boolean;
|
||||||
|
key?: string
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user