This commit is contained in:
juguohong
2025-08-31 20:24:42 +08:00
parent d92419f3c5
commit 4e68db2476
28 changed files with 921 additions and 206 deletions

View File

@@ -3,14 +3,14 @@
.listTopSearchWrapper {
padding: 0 15px;
position: sticky;
// position: sticky;
background: #fefefe;
z-index: 999;
// z-index: 999;
}
.isScroll {
border-bottom: 0.5px solid #0000000F;
}
// .isScroll {
// border-bottom: 0.5px solid #0000000F;
// }
.listTopFilterWrapper {
display: flex;

View File

@@ -8,15 +8,18 @@ import Taro, { usePageScroll, useReachBottom } from "@tarojs/taro";
import { useListStore } from "@/store/listStore";
import { useGlobalState } from "@/store/global";
import { View } from "@tarojs/components";
import CustomerNavBar from "@/components/CustomNavbar";
import CustomerNavBar from "@/container/listCustomNavbar";
import InputCustomerBar from "@/container/inputCustomerNavbar";
import GuideBar from "@/components/GuideBar";
import ListContainer from "@/container/listContainer";
import img from "@/config/images";
const ListPage = () => {
// 从 store 获取数据和方法
const store = useListStore() || {};
const { statusNavbarHeightInfo } = useGlobalState() || {};
const { totalHeight } = statusNavbarHeightInfo || {};
const {
isShowFilterPopup,
error,
@@ -33,11 +36,18 @@ const ListPage = () => {
quickFilterData,
distanceQuickFilter,
isScrollTop,
searchValue,
isShowInputCustomerNavBar,
} = store;
usePageScroll((res) => {
if (res?.scrollTop > 0 && !isScrollTop) {
updateState({ isScrollTop: true });
// if (res?.scrollTop > 0 && !isScrollTop) {
// updateState({ isScrollTop: true });
// }
if (res?.scrollTop >= totalHeight && !isScrollTop) {
updateState({ isShowInputCustomerNavBar: true });
} else {
updateState({ isShowInputCustomerNavBar: false });
}
});
@@ -105,28 +115,40 @@ const ListPage = () => {
});
};
const handleSearchClick = () => {
Taro.navigateTo({
url: "/pages/search/index",
});
}
return (
<>
<CustomerNavBar />
{!isShowInputCustomerNavBar ? (
<CustomerNavBar />
) : (
<InputCustomerBar icon={img.ICON_LIST_INPUT_LOGO} />
)}
<View className={styles.listPage}>
<View
className={`${styles.listTopSearchWrapper} ${
isScrollTop ? styles.isScroll : ""
}`}
style={{
top: statusNavbarHeightInfo?.totalHeight,
}}
// style={{
// top: statusNavbarHeightInfo?.totalHeight,
// }}
>
<SearchBar
handleFilterIcon={toggleShowPopup}
isSelect={filterCount > 0}
filterCount={filterCount}
onChange={handleSearchChange}
value={searchValue}
onInputClick={handleSearchClick}
/>
{/* 综合筛选 */}
{isShowFilterPopup && (
<div>
<View>
<FilterPopup
loading={loading}
onCancel={toggleShowPopup}
@@ -138,7 +160,7 @@ const ListPage = () => {
onClose={toggleShowPopup}
statusNavbarHeigh={statusNavbarHeightInfo?.totalHeight}
/>
</div>
</View>
)}
{/* 筛选 */}
<div className={styles.listTopFilterWrapper}>
@@ -161,13 +183,13 @@ const ListPage = () => {
</div>
</View>
{/* 列表内容 */}
<ListContainer
data={matches}
loading={loading}
error={error}
reload={refreshMatches}
/>
{/* 列表内容 */}
<ListContainer
data={matches}
loading={loading}
error={error}
reload={refreshMatches}
/>
</View>
<GuideBar currentPage="list" />

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '',
navigationStyle: 'custom',
})

120
src/pages/search/index.scss Normal file
View File

@@ -0,0 +1,120 @@
.listSearchContainer {
padding: 0 15px;
.icon16 {
width: 16px;
height: 16px;
}
.topSearch {
padding: 10px 16px 5px 12px;
display: flex;
align-items: center;
height: 44px;
box-sizing: border-box;
gap: 10px;
border-radius: 44px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: #FFF;
box-shadow: 0 4px 48px 0 rgba(0, 0, 0, 0.08);
.nut-input {
padding: 0;
height: 100%;
}
}
.searchRight {
display: flex;
align-items: center;
gap: 12px;
.searchLine {
width: 1px;
height: 20px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.06);
}
.searchText {
color: #000000;
font-size: 16px;
font-weight: 600;
line-height: 20px
}
}
.searchIcon {
width: 20px;
height: 20px;
}
.historySearchTitleWrapper {
display: flex;
padding: 12px 15px;
justify-content: space-between;
align-items: flex-end;
align-self: stretch;
.historySearchTitle,
.historySearchClear {
color: #000;
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
.historySearchClear {
color: #9a9a9a;
display: flex;
align-items: center;
gap: 4px;
}
}
.historySearchList {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.historySearchItem {
flex-shrink: 0;
flex-grow: 0;
display: flex;
height: 28px;
padding: 4px 12px;
justify-content: center;
align-items: center;
gap: 2px;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.03);
}
}
.searchSuggestion {
padding: 6px 0;
.searchSuggestionItem {
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
.searchSuggestionItemLeft {
display: flex;
align-items: center;
gap: 12px;
color: rgba(60, 60, 67, 0.60);
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.highlight {
color: #000000;
}
}
}
}

201
src/pages/search/index.tsx Normal file
View File

@@ -0,0 +1,201 @@
import InputCustomerBar from "@/container/inputCustomerNavbar";
import CustomerNavbarBack from "@/components/CustomerNavbarBack";
import { View, Image, Text } from "@tarojs/components";
import { Input } from "@nutui/nutui-react-taro";
import { useEffect, useMemo, useRef } from "react";
import { useListState } from "@/store/listStore";
import img from "@/config/images";
import "./index.scss";
import Taro from "@tarojs/taro";
const ListSearch = () => {
const {
searchValue,
updateState,
getSearchHistory,
searchHistory = [],
clearHistory,
searchSuggestion,
suggestionList,
isShowSuggestion,
isShowInputCustomerNavBar,
} = useListState() || {};
const ref = useRef<any>(null);
useEffect(() => {
getSearchHistory();
}, []);
useEffect(() => {
if (ref?.current) {
ref.current.focus();
}
}, [ref.current]);
const regex = useMemo(() => {
return new RegExp(searchValue, "gi");
}, [searchValue]);
/**
* @description 输入
* @param value
*/
const handleChange = (value: string) => {
updateState({ searchValue: value });
if (value) {
searchSuggestion(value);
}
};
/**
* @description 点击清空输入内容
*/
const handleClear = () => {
updateState({ searchValue: "" });
};
/**
* @description 点击历史搜索
* @param value
*/
const handleHistoryClick = (value: string) => {
updateState({ searchValue: value });
handleSearch();
};
/**
* @description 清空历史搜索
*/
const handleClearHistory = () => {
clearHistory();
};
/**
* @description 点击联想词
*/
const handleSuggestionSearch = (val: string) => {
updateState({
searchValue: val,
isShowSuggestion: false,
});
handleSearch();
};
/**
* @description 点击搜索
*/
const handleSearch = () => {
Taro.navigateTo({
url: `/pages/searchResult/index`,
});
}
// 是否显示清空图标
const isShowClearIcon = searchValue && searchValue?.length > 0;
// 是否显示搜索历史
const isShowHistory =
!isShowClearIcon && searchHistory && searchHistory?.length > 0;
return (
<>
{!isShowInputCustomerNavBar ? (
<CustomerNavbarBack />
) : (
<InputCustomerBar icon={img.ICON_LIST_INPUT_LOGO} />
)}
<View className="listSearchContainer">
{/* 搜索 */}
<View className="topSearch">
<Image className="searchIcon" src={img.ICON_LIST_SEARCH_SEARCH} />
<Input
placeholder="搜索上海的球局和场地"
value={searchValue}
defaultValue={searchValue}
onChange={handleChange}
onClear={handleClear}
autoFocus
clearable={false}
ref={ref}
/>
<View className="searchRight">
{isShowClearIcon && (
<Image
className="clearIcon icon16"
src={img.ICON_LIST_SEARCH_CLEAR}
onClick={handleClear}
/>
)}
<View className="searchLine" />
<Text className="searchText" onClick={handleSearch}></Text>
</View>
</View>
{/* 联想词 */}
{isShowSuggestion && (
<View className="searchSuggestion">
{(suggestionList || [])?.map((item) => {
// 替换匹配文本为高亮版本
const highlightedText = item.replace(regex, (match) => {
// 如果匹配不到,则返回原文本
if (!match) return match;
return `<Text class="highlight">${match}</Text>`;
});
return (
<View
className="searchSuggestionItem"
onClick={() => handleSuggestionSearch(item)}
>
<View className="searchSuggestionItemLeft">
<Image
className="icon16"
src={img.ICON_LIST_SEARCH_SEARCH}
/>
<Text
dangerouslySetInnerHTML={{ __html: highlightedText }}
></Text>
</View>
<Image
className="icon16"
src={img.ICON_LIST_SEARCH_SUGGESTION}
/>
</View>
);
})}
</View>
)}
{/* 历史搜索 */}
{!isShowClearIcon && (
<View className="historySearch">
<View className="historySearchTitleWrapper">
<View className="historySearchTitle"></View>
<View className="historySearchClear" onClick={handleClearHistory}>
<Text></Text>
<Image
className="clearIcon icon16"
src={img.ICON_LIST_SEARCH_CLEAR_HISTORY}
/>
</View>
</View>
{isShowHistory && (
<View className="historySearchList">
{(searchHistory || [])?.map((item) => {
return (
<Text
className="historySearchItem"
onClick={() => handleHistoryClick(item)}
>
{item}
</Text>
);
})}
</View>
)}
</View>
)}
</View>
</>
);
};
export default ListSearch;

View File

@@ -0,0 +1,3 @@
.menuFilter {
color: red;
}

View File

@@ -0,0 +1,65 @@
import { View } from "@tarojs/components";
// import styles from "./index.scss";
import CityFilter from "@/components/CityFilter";
import Menu from "@/components/Menu";
import { useListState } from "@/store/listStore";
import ListContainer from "@/container/listContainer";
import "./index.scss";
const SearchResult = () => {
const {
distanceData,
quickFilterData,
distanceQuickFilter,
updateState,
matches,
loading,
error,
refreshMatches,
} = useListState() || {};
// 距离筛选
const handleDistanceOrQuickChange = (name, value) => {
updateState({
distanceQuickFilter: {
...distanceQuickFilter,
[name]: value,
},
});
};
return (
<View>
{/* 筛选 */}
<View className='searchResultFilterWrapper'>
{/* 全城筛选 */}
<CityFilter
options={distanceData}
value={distanceQuickFilter?.distance}
wrapperClassName='menuFilter'
onChange={handleDistanceOrQuickChange}
name="distance"
/>
{/* 智能排序 */}
<Menu
options={quickFilterData}
value={distanceQuickFilter?.quick}
onChange={handleDistanceOrQuickChange}
wrapperClassName='menuFilter'
name="quick"
/>
</View>
{/* 列表内容 */}
<ListContainer
data={matches}
loading={loading}
error={error}
reload={refreshMatches}
/>
</View>
);
};
export default SearchResult;