This commit is contained in:
juguohong
2025-08-19 00:43:29 +08:00
parent 2b7c9497c6
commit 7a7ab85a82
13 changed files with 174 additions and 84 deletions

View File

@@ -1,6 +1,7 @@
.menuWrap {
padding: 5px 20px 10px;
.menuItem {
width: 100vw;
left: 0;
border-bottom-left-radius: 30px;
border-bottom-right-radius: 30px;

View File

@@ -12,7 +12,7 @@ interface IProps {
}
const MenuComponent = (props: IProps) => {
const { value, onChange, wrapperClassName, itemClassName } = props;
const { value, onChange, wrapperClassName, itemClassName, options } = props;
const [isChange, setIsChange] = useState(false);
const itemRef = useRef(null);
@@ -22,12 +22,7 @@ const MenuComponent = (props: IProps) => {
onChange && onChange(value);
};
const options: BubbleOption[] = [
{ id: 0, label: "全城", value: "0" },
{ id: 1, label: "3km", value: "3" },
{ id: 2, label: "5km", value: "5" },
{ id: 3, label: "10km", value: "10" },
];
return (
<Menu
@@ -38,7 +33,7 @@ const MenuComponent = (props: IProps) => {
>
<Menu.Item
title="全城"
className={`${styles.menuItem} ${itemClassName}`}
className={`${styles.menuItem} ${itemClassName ? itemClassName : ''}`}
ref={itemRef}
>
<div className={styles.positionWrap}>

View File

@@ -1,6 +1,8 @@
.menuWrap {
position: static;
padding: 5px 20px 10px;
.menuItem {
width: 100vw;
left: 0;
border-bottom-left-radius: 30px;
border-bottom-right-radius: 30px;

View File

@@ -6,7 +6,6 @@
--nutui-searchbar-input-text-color: #000000;
--nutui-searchbar-input-padding: 0 0 0 10px;
--nutui-searchbar-padding:0 15px;
// --nutui-searchbar-background: #ffffff;
:global(.nut-searchbar-content) {
box-shadow: 0 4px 48px #00000014;
}

View File

@@ -1,22 +1,26 @@
import { SearchBar } from "@nutui/nutui-react-taro";
// import {ICON_FILTER} from '../../config/images.js'
import styles from "./index.module.scss";
import { SearchBar } from '@nutui/nutui-react-taro'
import styles from './index.module.scss'
const SearchBarComponent = () => {
const SearchBarComponent = (props: IProps) => {
// console.log('===', ICON_FILTER)
const { handleFilterIcon } = props;
return (
<>
<SearchBar
<SearchBar
// leftIn={
// <div>123</div>
// }
right={
<div className={styles.searchBarRight}></div>
<div className={styles.searchBarRight} onClick={handleFilterIcon}>
</div>
}
className={styles.searchBar}
placeholder='搜索上海的球局和场地'
placeholder="搜索上海的球局和场地"
/>
</>
)
}
);
};
export default SearchBarComponent
export default SearchBarComponent;

View File

@@ -8,4 +8,5 @@ export default {
ICON_COST: require('@/static/publishBall/icon-cost.svg'),
ICON_TIPS: require('@/static/publishBall/icon-tips.svg'),
ICON_ARROW_RIGHT: require('@/static/publishBall/icon-arrow-right.svg'),
ICON_FILTER: require('@/static/list/icon-filter.svg'),
}

View File

@@ -3,6 +3,8 @@ import Range from "../../components/Range";
import Bubble, { BubbleOption } from "../../components/Bubble";
import styles from "./filterPopup.module.scss";
import TitleComponent from "src/components/Title";
import { Button } from "@nutui/nutui-react-taro";
import { useListStore } from "../../store/listStore";
const timeOptions: BubbleOption[] = [
{ id: 1, label: "晨间 6:00-10:00", value: "morning" },
@@ -19,7 +21,14 @@ const locationOptions: BubbleOption[] = [
{ id: 3, label: "半室外", value: "3" },
];
const FilterPopup = () => {
interface IProps {
onCancel: () => void;
onConfirm: () => void;
loading: boolean;
}
const FilterPopup = (props: IProps) => {
const { loading, onCancel, onConfirm } = props;
return (
<>
<Popup
@@ -28,9 +37,6 @@ const FilterPopup = () => {
position="top"
round
closeOnOverlayClick={false}
onClose={() => {
// setShowTop(false)
}}
>
<div className={styles.filterPopupWrapper}>
{/* 时间气泡选项 */}
@@ -63,6 +69,15 @@ const FilterPopup = () => {
columns={3}
/>
</div>
{/* 按钮 */}
<div className={styles.filterPopupBtnWrapper}>
<Button className={styles.btn} type="default" onClick={onCancel}>
</Button>
<Button className={`${styles.btn} ${styles.confirm}`} type="default" loading={loading} onClick={onConfirm}>
332
</Button>
</div>
</div>
</Popup>
</>

View File

@@ -1,8 +1,35 @@
.filterPopupWrapper {
position: relative;
$m18: 18px;
padding: $m18;
.filterPopupRange {
margin-top: $m18;
margin-bottom: $m18;
}
.filterPopupBtnWrapper {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
position: sticky;
bottom: 0;
background-color: #ffffff;
padding: 8px 0;
.btn {
flex: 1;
}
--nutui-button-border-width: 0.5px;
--nutui-button-default-border-color: #0000000F;
--nutui-button-border-radius: 16px;
--nutui-button-default-height: 44px;
--nutui-button-default-color: #000000;
.confirm {
--nutui-button-default-color: #ffffff;
--nutui-button-default-background-color: #000000;
}
}
}

View File

@@ -0,0 +1,15 @@
.listPage {
background-color: #fafafa;
padding: 0 15px;
.listTopFilterWrapper {
display: flex;
align-items: center;
padding: 5px 0 10px;
gap: 5px;
}
.menuFilter {
padding: 0;
}
}

View File

@@ -1,29 +1,26 @@
import ListItem from "../../components/ListItem";
import List from "../../components/List";
import Bubble from "../../components/Bubble/example";
import Range from "../../components/Range/example";
import Menu from "../../components/Menu/example";
import CityFilter from "../../components/CityFilter/example";
import Menu from "../../components/Menu";
import CityFilter from "../../components/CityFilter";
import SearchBar from "../../components/SearchBar";
import FilterPopup from "./FilterPopup";
import "./index.scss";
import styles from "./index.module.scss";
import { useEffect } from "react";
import Taro from "@tarojs/taro";
import {
useTennisMatches,
useTennisLoading,
useTennisError,
useTennisLastRefresh,
useTennisActions,
} from "../../store/listStore";
import { useListStore } from "../../store/listStore";
const ListPage = () => {
// 从 store 获取数据和方法
const matches = useTennisMatches();
const loading = useTennisLoading();
const error = useTennisError();
const lastRefreshTime = useTennisLastRefresh();
const { fetchMatches, refreshMatches, clearError } = useTennisActions();
const {
isShowFilterPopup,
error,
matches,
loading,
fetchMatches,
refreshMatches,
clearError,
updateState,
} = useListStore() || {};
useEffect(() => {
// 页面加载时获取数据
@@ -146,22 +143,43 @@ const ListPage = () => {
);
}
return (
<div>
<SearchBar />
{/* 综合筛选 */}
<div>
<FilterPopup />
</div>
{/* 筛选 */}
<div>
{/* 全城筛选 */}
<CityFilter />
{/* 智能排序 */}
<Menu />
</div>
const toggleShowPopup = () => {
updateState({ isShowFilterPopup: !isShowFilterPopup });
};
const cityOptions: BubbleOption[] = [
{ id: 0, label: "全城", value: "0" },
{ id: 1, label: "3km", value: "3" },
{ id: 2, label: "5km", value: "5" },
{ id: 3, label: "10km", value: "10" },
];
const options = [
{ text: "默认排序", value: "a" },
{ text: "好评排序", value: "b" },
{ text: "销量排序", value: "c" },
];
return (
<div className={styles.listPage}>
<SearchBar handleFilterIcon={toggleShowPopup} />
{/* 综合筛选 */}
{isShowFilterPopup && (
<div>
<FilterPopup
loading={loading}
onCancel={toggleShowPopup}
onConfirm={toggleShowPopup}
/>
</div>
)}
{/* 筛选 */}
<div className={ styles.listTopFilterWrapper}>
{/* 全城筛选 */}
<CityFilter options={cityOptions} value="1" onChange={() => { }} wrapperClassName={styles.menuFilter} />
{/* 智能排序 */}
<Menu options={options} value="a" onChange={() => { }} wrapperClassName={styles.menuFilter} />
</div>
{/* 列表内容 */}
<List>

View File

@@ -0,0 +1,11 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.2917 4.16666H14.7917" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.4583 2.5V5.83333" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.4584 4.16666H2.29175" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.62508 10H2.29175" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.95825 8.33333V11.6667" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.1249 10H8.95825" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.2917 15.8333H14.7917" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.4583 14.1667V17.5" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.4584 15.8333H2.29175" stroke="black" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -16,15 +16,16 @@ export interface TennisMatch {
}
// Store 状态接口
interface TennisState {
interface ListState {
matches: TennisMatch[]
loading: boolean
error: string | null
lastRefreshTime: string | null
isShowFilterPopup: boolean
}
// Store Actions 接口
interface TennisActions {
interface ListActions {
fetchMatches: (params?: {
page?: number
pageSize?: number
@@ -33,36 +34,39 @@ interface TennisActions {
}) => Promise<void>
refreshMatches: () => Promise<void>
clearError: () => void
updateState: (payload: Record<string, any>) => void
}
// 完整的 Store 类型
type TennisStore = TennisState & TennisActions
type TennisStore = ListState & ListActions
// 创建 store
export const useTennisStore = create<TennisStore>()((set, get) => ({
export const useListStore = create<TennisStore>()((set, get) => ({
// 初始状态
matches: [],
loading: false,
error: null,
lastRefreshTime: null,
// 是否展示综合筛选弹窗
isShowFilterPopup: false,
// 获取比赛数据
fetchMatches: async (params) => {
set({ loading: true, error: null })
try {
const matches = await getTennisMatches(params)
set({
matches,
loading: false,
lastRefreshTime: new Date().toISOString()
set({
matches,
loading: false,
lastRefreshTime: new Date().toISOString()
})
console.log('Store: 成功获取网球比赛数据:', matches.length, '条')
} catch (error) {
const errorMessage = error instanceof Error ? error.message : '未知错误'
set({
error: errorMessage,
loading: false
set({
error: errorMessage,
loading: false
})
console.error('Store: 获取网球比赛数据失败:', errorMessage)
}
@@ -71,20 +75,20 @@ export const useTennisStore = create<TennisStore>()((set, get) => ({
// 刷新比赛数据
refreshMatches: async () => {
set({ loading: true, error: null })
try {
const matches = await getTennisMatches()
set({
matches,
loading: false,
lastRefreshTime: new Date().toISOString()
set({
matches,
loading: false,
lastRefreshTime: new Date().toISOString()
})
console.log('Store: 成功刷新网球比赛数据:', matches.length, '条')
} catch (error) {
const errorMessage = error instanceof Error ? error.message : '未知错误'
set({
error: errorMessage,
loading: false
set({
error: errorMessage,
loading: false
})
console.error('Store: 刷新网球比赛数据失败:', errorMessage)
}
@@ -93,16 +97,14 @@ export const useTennisStore = create<TennisStore>()((set, get) => ({
// 清除错误信息
clearError: () => {
set({ error: null })
},
// 更新store数据
updateState: (payload: Record<string, any>) => {
set({
...(payload || {})
})
}
}))
// 导出便捷的 hooks
export const useTennisMatches = () => useTennisStore((state) => state.matches)
export const useTennisLoading = () => useTennisStore((state) => state.loading)
export const useTennisError = () => useTennisStore((state) => state.error)
export const useTennisLastRefresh = () => useTennisStore((state) => state.lastRefreshTime)
export const useTennisActions = () => useTennisStore((state) => ({
fetchMatches: state.fetchMatches,
refreshMatches: state.refreshMatches,
clearError: state.clearError
}))
export const useListState = () => useListStore((state) => state)