From e6124131e7a9989a8065e86bb3d45de4696dfe4f Mon Sep 17 00:00:00 2001 From: juguohong Date: Sat, 23 Aug 2025 15:40:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=BB=BC=E5=90=88=E7=AD=9B?= =?UTF-8?q?=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Bubble/index.tsx | 7 ++- src/components/CityFilter/index.module.scss | 14 +++++- src/components/List/index.scss | 1 - src/components/Range/index.tsx | 19 ++++---- src/components/SearchBar/index.module.scss | 33 +++++++++++-- src/components/SearchBar/index.tsx | 35 ++++++++++---- src/config/images.js | 2 + src/pages/list/FilterPopup.tsx | 53 +++++++++++++++------ src/pages/list/index.tsx | 41 +++++++++++++--- src/static/list/icon-filter-selected.svg | 11 +++++ src/static/list/icon-search.svg | 5 ++ src/store/listStore.ts | 46 ++++++++++++++++++ 12 files changed, 219 insertions(+), 48 deletions(-) create mode 100644 src/static/list/icon-filter-selected.svg create mode 100644 src/static/list/icon-search.svg diff --git a/src/components/Bubble/index.tsx b/src/components/Bubble/index.tsx index 264de4e..59af227 100644 --- a/src/components/Bubble/index.tsx +++ b/src/components/Bubble/index.tsx @@ -15,6 +15,7 @@ export interface BubbleProps { options: BubbleOption[]; value?: string | number | (string | number)[]; onChange?: ( + name: string, value: string | number | (string | number)[], option: BubbleOption | BubbleOption[] ) => void; @@ -26,6 +27,7 @@ export interface BubbleProps { itemClassName?: string; style?: React.CSSProperties; disabled?: boolean; + name: string; } const Bubble: React.FC = ({ @@ -40,6 +42,7 @@ const Bubble: React.FC = ({ itemClassName = "", style = {}, disabled = false, + name, }) => { const [selectedValues, setSelectedValues] = useState<(string | number)[]>([]); @@ -74,9 +77,9 @@ const Bubble: React.FC = ({ const selectedOptions = options.filter((opt) => newSelectedValues.includes(opt.value) ); - onChange(newSelectedValues, selectedOptions); + onChange(name, newSelectedValues, selectedOptions); } else { - onChange(option.value, option); + onChange(name, option.value, option); } } }; diff --git a/src/components/CityFilter/index.module.scss b/src/components/CityFilter/index.module.scss index ef6d454..ec1ee63 100644 --- a/src/components/CityFilter/index.module.scss +++ b/src/components/CityFilter/index.module.scss @@ -1,17 +1,24 @@ .menuWrap { padding: 5px 20px 10px; + .menuItem { width: 100vw; left: 0; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; } + + .menuItem { + position: fixed; + } + &.active { .nut-menu-bar { background-color: #000000; color: #ffffff; } } + :global(.nut-menu-bar) { color: #000000; line-height: 1; @@ -23,6 +30,7 @@ line-height: 28px; font-size: 14px; width: max-content; + .nut-menu-title { color: inherit !important; font-weight: 600; @@ -49,13 +57,15 @@ .cityName { font-size: 13px; font-weight: 400; - color: #3C3C43; + color: #3c3c43; } + .distanceWrap { margin-bottom: 16px; width: 100%; } + .distanceBubbleItem { width: auto; } -} +} \ No newline at end of file diff --git a/src/components/List/index.scss b/src/components/List/index.scss index 391a9aa..5742c9e 100644 --- a/src/components/List/index.scss +++ b/src/components/List/index.scss @@ -1,6 +1,5 @@ .list { background: #fafafa; - margin-top: 12px; display: flex; flex-direction: column; gap: 5px; diff --git a/src/components/Range/index.tsx b/src/components/Range/index.tsx index 84fe4b1..e4d0f78 100644 --- a/src/components/Range/index.tsx +++ b/src/components/Range/index.tsx @@ -8,9 +8,10 @@ interface RangeProps { max?: number; step?: number; value?: [number, number]; - onChange?: (value: [number, number]) => void; + onChange?: (name: string, value: [number, number]) => void; disabled?: boolean; className?: string; + name: string; } const NtrpRange: React.FC = ({ @@ -21,16 +22,18 @@ const NtrpRange: React.FC = ({ onChange, disabled = false, className, + name, }) => { const [currentValue, setCurrentValue] = useState<[number, number]>(value); - +console.log('===currentValue', currentValue) useEffect(() => { + console.log('===rrr', value) value && setCurrentValue(value); }, [JSON.stringify(value || [])]); const handleChange = (val: [number, number]) => { setCurrentValue(val); - onChange?.(val); + onChange?.(name, val); }; const marks = useMemo(() => { @@ -50,13 +53,9 @@ const NtrpRange: React.FC = ({ }, [JSON.stringify(currentValue || []), min, max]); return ( -
+
- {/*
-
icon
-

NTRP水平区间

-
*/} - +

{rangContent}

@@ -68,7 +67,7 @@ const NtrpRange: React.FC = ({ min={min} max={max} step={step} - // value={currentValue} + value={currentValue} onEnd={handleChange} disabled={disabled} defaultValue={[min, max]} diff --git a/src/components/SearchBar/index.module.scss b/src/components/SearchBar/index.module.scss index 632fe92..cc20edd 100644 --- a/src/components/SearchBar/index.module.scss +++ b/src/components/SearchBar/index.module.scss @@ -5,18 +5,45 @@ --nutui-searchbar-content-border-radius: 44px; --nutui-searchbar-input-text-color: #000000; --nutui-searchbar-input-padding: 0 0 0 10px; - --nutui-searchbar-padding:0 15px; + --nutui-searchbar-padding: 10px 0 0 0; :global(.nut-searchbar-content) { box-shadow: 0 4px 48px #00000014; } .searchBarRight { + position: relative; width: 44px; height: 44px; border-radius: 50%; - border: 1px solid #0000000F; + border: 1px solid #0000000f; background-color: #ffffff; display: flex; align-items: center; justify-content: center; + &.active { + background-color: #000000; + } } -} \ No newline at end of file + .filterIcon { + width: 20px; + height: 20px; + } + .filterCount { + background-color: #000000; + position: absolute; + width: 18px; + height: 18px; + border: 2px solid #ffffff; + border-radius: 50%; + right: -5px; + bottom: -5px; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + } + .searchIcon { + width: 20px; + height: 20px; + } +} diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index 9456d55..9d4827d 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -1,21 +1,38 @@ import { SearchBar } from "@nutui/nutui-react-taro"; -// import {ICON_FILTER} from '../../config/images.js' +import { View, Text, Image } from "@tarojs/components"; +import img from "../../config/images"; import styles from "./index.module.scss"; +interface IProps { + handleFilterIcon: () => void; + isSelect: boolean; + filterCount: number; +} + const SearchBarComponent = (props: IProps) => { - // console.log('===', ICON_FILTER) - const { handleFilterIcon } = props; + const { handleFilterIcon, isSelect, filterCount } = props; return ( <> 123
- // } - right={ -
- 筛 + leftIn={ +
+
} + right={ + + + {filterCount} + + } className={styles.searchBar} placeholder="搜索上海的球局和场地" /> diff --git a/src/config/images.js b/src/config/images.js index 2f42a82..9effb04 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -9,4 +9,6 @@ export default { 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'), + ICON_FILTER_SELECTED: require('@/static/list/icon-filter-selected.svg'), + ICON_SEARCH: require('@/static/list/icon-search.svg'), } \ No newline at end of file diff --git a/src/pages/list/FilterPopup.tsx b/src/pages/list/FilterPopup.tsx index a4c19f3..5598593 100644 --- a/src/pages/list/FilterPopup.tsx +++ b/src/pages/list/FilterPopup.tsx @@ -4,15 +4,14 @@ 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" }, - { id: 2, label: "上午 10:00-12:00", value: "forenoon" }, - { id: 3, label: "中午 12:00-14:00", value: "noon" }, - { id: 4, label: "下午 14:00-18:00", value: "afternoon" }, - { id: 5, label: "晚上 18:00-22:00", value: "evening" }, - { id: 6, label: "夜间 22:00-24:00", value: "night" }, + { 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" }, ]; const locationOptions: BubbleOption[] = [ @@ -24,11 +23,25 @@ const locationOptions: BubbleOption[] = [ interface IProps { onCancel: () => void; onConfirm: () => void; + onChange: (params: Record) => void; loading: boolean; + filterOptions: Record; + onClear: () => void; } const FilterPopup = (props: IProps) => { - const { loading, onCancel, onConfirm } = props; + const { loading, onCancel, onConfirm, onChange, filterOptions, onClear } = props; + console.log('===filterOptions', filterOptions) + + const handleFilterChange = (name, value) => { + onChange({ [name]: value }); + }; + + const handleClearFilter = () => { + onClear() + onCancel(); + } + return ( <> { destroyOnClose position="top" round - closeOnOverlayClick={false} + closeOnOverlayClick={true} >
{/* 时间气泡选项 */} {}} - onChange={(value) => {}} + value={filterOptions?.time} + onChange={handleFilterChange} layout="grid" size="small" columns={3} + name="time" /> {/* 范围选择 */} @@ -55,6 +69,9 @@ const FilterPopup = (props: IProps) => { max={5.0} step={0.5} className={styles.filterPopupRange} + onChange={handleFilterChange} + value={filterOptions?.ntrp} + name='ntrp' /> {/* 场次气泡选项 */} @@ -62,19 +79,25 @@ const FilterPopup = (props: IProps) => { {}} - onChange={(value) => {}} + value={filterOptions?.site} + onChange={handleFilterChange} layout="grid" size="small" columns={3} + name='site' />
{/* 按钮 */}
- -
diff --git a/src/pages/list/index.tsx b/src/pages/list/index.tsx index 525fddc..0914616 100644 --- a/src/pages/list/index.tsx +++ b/src/pages/list/index.tsx @@ -4,7 +4,7 @@ import Menu from "../../components/Menu"; import CityFilter from "../../components/CityFilter"; import SearchBar from "../../components/SearchBar"; import FilterPopup from "./FilterPopup"; -import styles from "./index.module.scss"; +import styles from "./index.module.scss"; import { useEffect } from "react"; import Taro from "@tarojs/taro"; import { useListStore } from "../../store/listStore"; @@ -20,6 +20,10 @@ const ListPage = () => { refreshMatches, clearError, updateState, + filterCount, + updateFilterOptions, // 更新筛选条件 + filterOptions, + clearFilterOptions, } = useListStore() || {}; useEffect(() => { @@ -147,6 +151,10 @@ const ListPage = () => { updateState({ isShowFilterPopup: !isShowFilterPopup }); }; + const updateFilterSelect = (params) => { + updateState(params); + }; + const cityOptions: BubbleOption[] = [ { id: 0, label: "全城", value: "0" }, { id: 1, label: "3km", value: "3" }, @@ -154,15 +162,23 @@ const ListPage = () => { { id: 3, label: "10km", value: "10" }, ]; - const options = [ + const options = [ { text: "默认排序", value: "a" }, { text: "好评排序", value: "b" }, { text: "销量排序", value: "c" }, ]; + const handleUpdateFilterOptions = (params: Record) => { + updateFilterOptions(params); + }; + return (
- + 0} + filterCount={filterCount} + /> {/* 综合筛选 */} {isShowFilterPopup && (
@@ -170,15 +186,28 @@ const ListPage = () => { loading={loading} onCancel={toggleShowPopup} onConfirm={toggleShowPopup} + onChange={handleUpdateFilterOptions} + filterOptions={filterOptions} + onClear={clearFilterOptions} />
)} {/* 筛选 */} -
+
{/* 全城筛选 */} - { }} wrapperClassName={styles.menuFilter} /> + {}} + wrapperClassName={styles.menuFilter} + /> {/* 智能排序 */} - { }} wrapperClassName={styles.menuFilter} /> + {}} + wrapperClassName={styles.menuFilter} + />
{/* 列表内容 */} diff --git a/src/static/list/icon-filter-selected.svg b/src/static/list/icon-filter-selected.svg new file mode 100644 index 0000000..4b51f02 --- /dev/null +++ b/src/static/list/icon-filter-selected.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/static/list/icon-search.svg b/src/static/list/icon-search.svg new file mode 100644 index 0000000..3ca4401 --- /dev/null +++ b/src/static/list/icon-search.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/store/listStore.ts b/src/store/listStore.ts index 3a61d67..f0b2cd7 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -22,6 +22,16 @@ interface ListState { error: string | null lastRefreshTime: string | null isShowFilterPopup: boolean + filterOptions: IFilterOptions + filterCount: number +} + +interface IFilterOptions { + location: string + time: string + ntrp: [number, number] + site: string + wanfa: string } // Store Actions 接口 @@ -35,11 +45,21 @@ interface ListActions { refreshMatches: () => Promise clearError: () => void updateState: (payload: Record) => void + updateFilterOptions: (payload: Record) => void + clearFilterOptions: () => void } // 完整的 Store 类型 type TennisStore = ListState & ListActions +const defaultFilterOptions: IFilterOptions = { + location: '', // 位置 + time: '', // 时间 + ntrp: [1.0, 5.0], // NTRP 水平区间 + site: '', // 场地类型 + wanfa: '', // 玩法 +}; + // 创建 store export const useListStore = create()((set, get) => ({ // 初始状态 @@ -49,6 +69,10 @@ export const useListStore = create()((set, get) => ({ lastRefreshTime: null, // 是否展示综合筛选弹窗 isShowFilterPopup: false, + // 综合筛选项 + filterOptions: defaultFilterOptions, + // 综合筛选 选择的筛选数量 + filterCount: 0, // 获取比赛数据 fetchMatches: async (params) => { @@ -98,8 +122,30 @@ export const useListStore = create()((set, get) => ({ clearError: () => { set({ error: null }) }, + + // 更新综合筛选项 + updateFilterOptions: (payload: Record) => { + const preFilterOptions = get()?.filterOptions || {} + const filterOptions = { ...preFilterOptions, ...payload } + const filterCount = Object.values(filterOptions).filter(Boolean).length + set({ + filterOptions, + filterCount + }) + }, + + // 清空综合筛选选项 + clearFilterOptions: () => { + set({ + filterOptions: defaultFilterOptions, + filterCount: 0 + }) + }, + // 更新store数据 updateState: (payload: Record) => { + const state = get(); + console.log('Store: 更新数据:', state); set({ ...(payload || {}) })