添加行政区选择
This commit is contained in:
5
.cursor/worktrees.json
Normal file
5
.cursor/worktrees.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"setup-worktree": [
|
||||||
|
"npm install"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
// 标签样式
|
// 标签样式
|
||||||
.bubbleLabel {
|
.bubbleLabel {
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
265
src/components/DistanceQuickFilterV2/index.scss
Normal file
265
src/components/DistanceQuickFilterV2/index.scss
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
.distanceQuickFilterWrap {
|
||||||
|
width: 100%;
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 全局覆盖 NutUI Menu 容器的 overflow 样式
|
||||||
|
|
||||||
|
|
||||||
|
.nut-menu-container-wrap {
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
width: auto !important;
|
||||||
|
border-bottom-left-radius: 30px;
|
||||||
|
border-bottom-right-radius: 30px;
|
||||||
|
background-color: #fafafa !important;
|
||||||
|
z-index: 1100 !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
max-height: auto !important;
|
||||||
|
height: 380px !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.nut-menu-container-content {
|
||||||
|
overflow: visible !important;
|
||||||
|
padding-left: 0px !important;
|
||||||
|
padding-right: 0px !important;
|
||||||
|
padding-bottom: 0px !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
max-height: auto !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.nut-menu-bar {
|
||||||
|
--nutui-menu-bar-line-height: 30px;
|
||||||
|
background-color: #fafafa !important; // 明确设置背景色,避免组件默认样式覆盖
|
||||||
|
box-shadow: unset;
|
||||||
|
// padding: 0 15px;
|
||||||
|
gap: 5px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nut-menu-title {
|
||||||
|
flex: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
height: 28px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.06); // 根据设计稿添加边框
|
||||||
|
background: #ffffff;
|
||||||
|
font-family: "PingFang SC"; // 根据设计稿设置字体
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px; // 1.4285714285714286em ≈ 20px
|
||||||
|
letter-spacing: -0.23px; // -1.6428571726594652% of 14px
|
||||||
|
}
|
||||||
|
|
||||||
|
.nut-menu-title.active {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.positionWrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-left: 20px ;
|
||||||
|
padding-right: 20px ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cityName {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #3c3c43;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distanceWrap {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distanceBubbleItem {
|
||||||
|
display: flex;
|
||||||
|
width: 80px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemIcon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:行政区选择区域样式
|
||||||
|
.districtWrap2 {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.districtContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch; // 让子元素高度一致
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
gap: 0;
|
||||||
|
flex: 1; // 占满父容器剩余空间
|
||||||
|
overflow: hidden; // 确保子元素不会超出圆角
|
||||||
|
|
||||||
|
.districtTitleBox {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
position: relative;
|
||||||
|
height: 100%; // 占满父容器高度
|
||||||
|
width: 82px;
|
||||||
|
|
||||||
|
.districtTitle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 10px 0 10px 20px; // 左侧标题的 padding
|
||||||
|
z-index: 1;
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4285714285714286em; // 20px
|
||||||
|
letter-spacing: -0.23px;
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.districtTitleBg {
|
||||||
|
width: 100%; // 自适应宽度
|
||||||
|
height: 100%; // 自适应高度
|
||||||
|
background-color: #f2f2f7; // 灰色背景
|
||||||
|
border-top-right-radius: 20px; // 右上角圆角
|
||||||
|
z-index: 0; // 在文字下方
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.districtOptionsWrapper {
|
||||||
|
flex: 1;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 290px;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
|
|
||||||
|
.districtOptionsList {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.districtItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 20px 8px 20px;
|
||||||
|
height: 38px;
|
||||||
|
color: rgba(60, 60, 67, 0.6);
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2857142857142858em; // 18px
|
||||||
|
letter-spacing: -0.2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.districtItemText {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickOptionsWrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/components/DistanceQuickFilterV2/index.tsx
Normal file
176
src/components/DistanceQuickFilterV2/index.tsx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { useRef, useState, useEffect } from "react";
|
||||||
|
import { Menu } from "@nutui/nutui-react-taro";
|
||||||
|
import { Image, View, ScrollView } from "@tarojs/components";
|
||||||
|
import img from "@/config/images";
|
||||||
|
import Bubble from "../Bubble";
|
||||||
|
import "./index.scss";
|
||||||
|
|
||||||
|
const DistanceQuickFilterV2 = (props) => {
|
||||||
|
const {
|
||||||
|
cityOptions,
|
||||||
|
quickOptions,
|
||||||
|
districtOptions = [], // 新增:行政区选项
|
||||||
|
onChange,
|
||||||
|
cityName,
|
||||||
|
quickName,
|
||||||
|
districtName = "district", // 新增:行政区字段名
|
||||||
|
cityValue,
|
||||||
|
quickValue,
|
||||||
|
districtValue, // 新增:行政区选中值
|
||||||
|
onMenuVisibleChange, // 菜单展开/收起回调
|
||||||
|
} = props;
|
||||||
|
const cityRef = useRef(null);
|
||||||
|
const quickRef = useRef(null);
|
||||||
|
const [changePosition, setChangePosition] = useState<number[]>([]);
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
// 全城筛选显示的标题 - 如果选择了行政区,显示行政区名称
|
||||||
|
const getCityTitle = () => {
|
||||||
|
if (districtValue) {
|
||||||
|
const selectedDistrict = districtOptions.find((item) => item.value === districtValue);
|
||||||
|
if (selectedDistrict) {
|
||||||
|
return selectedDistrict.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cityOptions.find((item) => item.value === cityValue)?.label;
|
||||||
|
};
|
||||||
|
const cityTitle = getCityTitle();
|
||||||
|
|
||||||
|
// 快捷筛选显示的标题
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 控制隐藏
|
||||||
|
index === 0 && (cityRef.current as any)?.toggle(false);
|
||||||
|
index === 1 && (quickRef.current as any)?.toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听菜单状态变化,通知父组件
|
||||||
|
useEffect(() => {
|
||||||
|
onMenuVisibleChange?.(isMenuOpen);
|
||||||
|
}, [isMenuOpen, onMenuVisibleChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Menu
|
||||||
|
className={`distanceQuickFilterWrap ${filterWrapperClassName}`}
|
||||||
|
onOpen={() => setIsMenuOpen(true)}
|
||||||
|
onClose={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<Menu.Item
|
||||||
|
title={cityTitle}
|
||||||
|
ref={cityRef}
|
||||||
|
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
|
||||||
|
>
|
||||||
|
<div className="positionWrap">
|
||||||
|
<p className="title">当前位置</p>
|
||||||
|
<p className="cityName">上海市</p>
|
||||||
|
</div>
|
||||||
|
<div className="distanceWrap">
|
||||||
|
<Bubble
|
||||||
|
options={cityOptions}
|
||||||
|
value={cityValue}
|
||||||
|
onChange={(name, value) => {
|
||||||
|
const singleValue = Array.isArray(value) ? value[0] : value;
|
||||||
|
handleChange(name, singleValue, 0);
|
||||||
|
}}
|
||||||
|
layout="grid"
|
||||||
|
size="small"
|
||||||
|
columns={4}
|
||||||
|
itemClassName="distanceBubbleItem"
|
||||||
|
name={cityName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* 新增:行政区选择区域 */}
|
||||||
|
{districtOptions.length > 0 && (
|
||||||
|
<div className="districtWrap2">
|
||||||
|
<div className="districtContent">
|
||||||
|
<div className="districtTitleBox">
|
||||||
|
<p className="districtTitle">行政区</p>
|
||||||
|
<div className="districtTitleBg"></div>
|
||||||
|
</div>
|
||||||
|
<ScrollView
|
||||||
|
className="districtOptionsWrapper"
|
||||||
|
scrollY
|
||||||
|
enhanced
|
||||||
|
showScrollbar={true}
|
||||||
|
>
|
||||||
|
<View className="districtOptionsList">
|
||||||
|
{districtOptions.map((item) => {
|
||||||
|
const active = districtValue === item?.value;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={item.value}
|
||||||
|
className={`districtItem ${active && "active"}`}
|
||||||
|
onClick={() => handleChange(districtName, item.value, 0)}
|
||||||
|
>
|
||||||
|
<View className="districtItemText">{item?.label}</View>
|
||||||
|
{active && (
|
||||||
|
<View>
|
||||||
|
<Image
|
||||||
|
className="itemIcon"
|
||||||
|
src={img.ICON_MENU_ITEM_SELECTED}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
title={quickTitle}
|
||||||
|
ref={quickRef}
|
||||||
|
defaultValue={quickValue}
|
||||||
|
>
|
||||||
|
<View className="quickOptionsWrapper">
|
||||||
|
{quickOptions.map((item) => {
|
||||||
|
const active = quickValue === item?.value;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={item.value}
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DistanceQuickFilterV2;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
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 { useGlobalState } from "@/store/global";
|
import { useGlobalState } from "@/store/global";
|
||||||
@@ -8,7 +8,10 @@ import { Input } from "@nutui/nutui-react-taro";
|
|||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { getCurrentFullPath } from "@/utils";
|
import { getCurrentFullPath } from "@/utils";
|
||||||
import { CityPicker as PopupPicker } from "@/components/Picker";
|
import { CityPickerV2 as PopupPicker } from "@/components/Picker";
|
||||||
|
|
||||||
|
// 城市缓存 key
|
||||||
|
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
config?: {
|
config?: {
|
||||||
@@ -81,6 +84,7 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
statusNavbarHeightInfo || {};
|
statusNavbarHeightInfo || {};
|
||||||
|
|
||||||
const [cityPopupVisible, setCityPopupVisible] = useState(false);
|
const [cityPopupVisible, setCityPopupVisible] = useState(false);
|
||||||
|
const hasShownLocationDialog = useRef(false); // 防止重复弹窗
|
||||||
|
|
||||||
// 监听城市选择器状态变化,通知父组件
|
// 监听城市选择器状态变化,通知父组件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -88,13 +92,59 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
}, [cityPopupVisible]);
|
}, [cityPopupVisible]);
|
||||||
|
|
||||||
const userInfo = useUserInfo();
|
const userInfo = useUserInfo();
|
||||||
const province = (userInfo as any)?.province || "";
|
const locationProvince = (userInfo as any)?.province || ""; // 定位获取的省份
|
||||||
const city = (userInfo as any)?.city || "";
|
// 只使用省份,不使用区域(city、district)
|
||||||
// const district = (userInfo as any)?.district || "";
|
|
||||||
|
|
||||||
|
// 初始化城市:优先使用缓存的定位信息,其次使用当前定位
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateArea(["中国", province, city]);
|
// 1. 尝试从缓存中读取上次的定位信息
|
||||||
}, [province, city]);
|
const cachedCity = (Taro as any).getStorageSync(CITY_CACHE_KEY);
|
||||||
|
|
||||||
|
if (cachedCity && cachedCity.length === 2) {
|
||||||
|
// 如果有缓存的定位信息,使用缓存
|
||||||
|
console.log("使用缓存的定位城市:", cachedCity);
|
||||||
|
updateArea(cachedCity);
|
||||||
|
|
||||||
|
// 如果当前定位省份已获取且与缓存不同,弹窗询问是否切换
|
||||||
|
if (locationProvince && cachedCity[1] !== locationProvince && !hasShownLocationDialog.current) {
|
||||||
|
hasShownLocationDialog.current = true;
|
||||||
|
showLocationConfirmDialog(locationProvince, cachedCity);
|
||||||
|
}
|
||||||
|
} else if (locationProvince) {
|
||||||
|
// 如果没有缓存但有定位信息,直接使用定位并保存到缓存
|
||||||
|
console.log("没有缓存,使用当前定位省份:", locationProvince);
|
||||||
|
const newArea: [string, string] = ["中国", locationProvince];
|
||||||
|
updateArea(newArea);
|
||||||
|
// 保存定位信息到缓存
|
||||||
|
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
||||||
|
}
|
||||||
|
}, [locationProvince]);
|
||||||
|
|
||||||
|
// 显示定位确认弹窗
|
||||||
|
const showLocationConfirmDialog = (detectedProvince: string, cachedCity: [string, string]) => {
|
||||||
|
(Taro as any).showModal({
|
||||||
|
title: "提示",
|
||||||
|
content: `检测到您当前位置在${detectedProvince},是否切换到${detectedProvince}?`,
|
||||||
|
confirmText: "是",
|
||||||
|
cancelText: "否",
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
// 用户选择"是",切换到当前定位城市
|
||||||
|
const newArea: [string, string] = ["中国", detectedProvince];
|
||||||
|
updateArea(newArea);
|
||||||
|
// 更新缓存为新的定位信息(只有定位的才缓存)
|
||||||
|
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
||||||
|
console.log("切换到当前定位城市并更新缓存:", detectedProvince);
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
handleCityChangeWithoutCache();
|
||||||
|
} else {
|
||||||
|
// 用户选择"否",保持缓存的定位城市
|
||||||
|
console.log("保持缓存的定位城市:", cachedCity[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// const currentAddress = city + district;
|
// const currentAddress = city + district;
|
||||||
|
|
||||||
@@ -123,6 +173,7 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
duration: 300,
|
duration: 300,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return; // 已经在列表页,只滚动到顶部,不需要跳转
|
||||||
}
|
}
|
||||||
(Taro as any).redirectTo({
|
(Taro as any).redirectTo({
|
||||||
url: "/main_pages/index", // 列表页
|
url: "/main_pages/index", // 列表页
|
||||||
@@ -147,8 +198,8 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
|
|
||||||
const area_city = area.at(-1);
|
const area_city = area.at(-1);
|
||||||
|
|
||||||
// 处理城市切换
|
// 处理城市切换(仅刷新数据,不保存缓存)
|
||||||
const handleCityChange = async (_newArea: any) => {
|
const handleCityChangeWithoutCache = async () => {
|
||||||
// 切换城市后,同时更新两个列表接口获取数据
|
// 切换城市后,同时更新两个列表接口获取数据
|
||||||
if (refreshBothLists) {
|
if (refreshBothLists) {
|
||||||
await refreshBothLists();
|
await refreshBothLists();
|
||||||
@@ -159,6 +210,15 @@ const HomeNavbar = (props: IProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理城市切换(用户手动选择)
|
||||||
|
const handleCityChange = async (_newArea: any) => {
|
||||||
|
// 用户手动选择的城市不保存到缓存
|
||||||
|
console.log("用户手动选择城市(不保存缓存):", _newArea);
|
||||||
|
|
||||||
|
// 切换城市后,同时更新两个列表接口获取数据
|
||||||
|
await handleCityChangeWithoutCache();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
className="homeNavbar"
|
className="homeNavbar"
|
||||||
|
|||||||
77
src/components/Picker/CityPickerV2.tsx
Normal file
77
src/components/Picker/CityPickerV2.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import CommonPopup from "@/components/CommonPopup";
|
||||||
|
import Picker from "./Picker";
|
||||||
|
|
||||||
|
interface PickerOption {
|
||||||
|
text: string | number;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CityPickerV2Props {
|
||||||
|
visible: boolean;
|
||||||
|
setvisible: (visible: boolean) => void;
|
||||||
|
options?: PickerOption[][]; // 两级数据:[国家列表, 省份/城市列表]
|
||||||
|
value?: (string | number)[]; // [国家value, 省份/城市value]
|
||||||
|
onChange?: (value: (string | number)[]) => void;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CityPickerV2 = ({
|
||||||
|
visible,
|
||||||
|
setvisible,
|
||||||
|
value = [],
|
||||||
|
onChange,
|
||||||
|
options = [],
|
||||||
|
style,
|
||||||
|
}: CityPickerV2Props) => {
|
||||||
|
const [defaultValue, setDefaultValue] = useState<(string | number)[]>(value);
|
||||||
|
|
||||||
|
// 当外部 value 变化时同步更新
|
||||||
|
useEffect(() => {
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
setDefaultValue(value);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const changePicker = (_options: any[], values: any, _columnIndex: number) => {
|
||||||
|
setDefaultValue(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
onChange?.(defaultValue);
|
||||||
|
setvisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogClose = () => {
|
||||||
|
setvisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CommonPopup
|
||||||
|
visible={visible}
|
||||||
|
onClose={dialogClose}
|
||||||
|
showHeader={false}
|
||||||
|
title={null}
|
||||||
|
hideFooter={false}
|
||||||
|
cancelText="取消"
|
||||||
|
confirmText="完成"
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
position="bottom"
|
||||||
|
round
|
||||||
|
zIndex={1000}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<Picker
|
||||||
|
visible={visible}
|
||||||
|
options={options}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
onChange={changePicker}
|
||||||
|
/>
|
||||||
|
</CommonPopup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CityPickerV2;
|
||||||
|
|
||||||
@@ -5,5 +5,6 @@ export type { PickerCommonRef } from './PickerCommon'
|
|||||||
export { default as CalendarUI } from './CalendarUI/CalendarUI'
|
export { default as CalendarUI } from './CalendarUI/CalendarUI'
|
||||||
export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard'
|
export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard'
|
||||||
export { default as CityPicker } from './CityPicker'
|
export { default as CityPicker } from './CityPicker'
|
||||||
|
export { default as CityPickerV2 } from './CityPickerV2'
|
||||||
export { default as DayDialog } from './DayDialog';
|
export { default as DayDialog } from './DayDialog';
|
||||||
export { default as HourDialog } from './HourDialog';
|
export { default as HourDialog } from './HourDialog';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useListStore } from "@/store/listStore";
|
|||||||
import { useGlobalState } from "@/store/global";
|
import { useGlobalState } from "@/store/global";
|
||||||
import { View, Image, Text, ScrollView } from "@tarojs/components";
|
import { View, Image, Text, ScrollView } from "@tarojs/components";
|
||||||
import ListContainer from "@/container/listContainer";
|
import ListContainer from "@/container/listContainer";
|
||||||
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
import DistanceQuickFilter from "@/components/DistanceQuickFilterV2";
|
||||||
import { updateUserLocation } from "@/services/userService";
|
import { updateUserLocation } from "@/services/userService";
|
||||||
import { useDictionaryStore } from "@/store/dictionaryStore";
|
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||||
import { saveImage, navigateTo } from "@/utils";
|
import { saveImage, navigateTo } from "@/utils";
|
||||||
@@ -56,8 +56,11 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
updateDistanceQuickFilter,
|
updateDistanceQuickFilter,
|
||||||
getCities,
|
getCities,
|
||||||
getCityQrCode,
|
getCityQrCode,
|
||||||
|
getDistricts,
|
||||||
area,
|
area,
|
||||||
cityQrCode,
|
cityQrCode,
|
||||||
|
districts,
|
||||||
|
gamesNum, // 新增:获取球局数量
|
||||||
} = store;
|
} = store;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -190,9 +193,10 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 分批异步执行初始化操作,避免阻塞首屏渲染
|
// 分批异步执行初始化操作,避免阻塞首屏渲染
|
||||||
// 1. 立即执行:获取城市和二维码(轻量操作)
|
// 1. 立即执行:获取城市、二维码和行政区列表(轻量操作)
|
||||||
getCities();
|
getCities();
|
||||||
getCityQrCode();
|
getCityQrCode();
|
||||||
|
getDistricts(); // 新增:获取行政区列表
|
||||||
|
|
||||||
// 2. 移除 fetchUserInfo 调用,因为父组件 main_pages/index.tsx 已经在授权成功后调用了
|
// 2. 移除 fetchUserInfo 调用,因为父组件 main_pages/index.tsx 已经在授权成功后调用了
|
||||||
// 这里直接使用 store 中的用户信息即可
|
// 这里直接使用 store 中的用户信息即可
|
||||||
@@ -324,10 +328,12 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
initDictionaryData();
|
initDictionaryData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const area_city = area?.at(-2) || "上海";
|
// 获取省份名称(area 格式: ["中国", "省份"])
|
||||||
|
const province = area?.at(1) || "上海";
|
||||||
|
|
||||||
function renderCityQrcode() {
|
function renderCityQrcode() {
|
||||||
let item = cityQrCode.find((item) => item.city_name === area_city);
|
// 根据省份查找对应的二维码
|
||||||
|
let item = cityQrCode.find((item) => item.city_name === province);
|
||||||
if (!item) item = cityQrCode.find((item) => item.city_name === "其他");
|
if (!item) item = cityQrCode.find((item) => item.city_name === "其他");
|
||||||
return (
|
return (
|
||||||
<View className={styles.cqContainer}>
|
<View className={styles.cqContainer}>
|
||||||
@@ -362,9 +368,13 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判定是否显示"暂无球局"页面
|
||||||
|
// 条件:省份不是上海 或 (已加载完成且球局数量为0)
|
||||||
|
const shouldShowNoGames = province !== "上海" ;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{area_city !== "上海" ? (
|
{shouldShowNoGames ? (
|
||||||
renderCityQrcode()
|
renderCityQrcode()
|
||||||
) : (
|
) : (
|
||||||
<View ref={scrollContextRef}>
|
<View ref={scrollContextRef}>
|
||||||
@@ -404,10 +414,13 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
|||||||
cityOptions={distanceData}
|
cityOptions={distanceData}
|
||||||
quickOptions={quickFilterData}
|
quickOptions={quickFilterData}
|
||||||
onChange={handleDistanceOrQuickChange}
|
onChange={handleDistanceOrQuickChange}
|
||||||
|
districtOptions={districts || []}
|
||||||
cityName="distanceFilter"
|
cityName="distanceFilter"
|
||||||
quickName="order"
|
quickName="order"
|
||||||
|
districtName="district"
|
||||||
cityValue={distanceQuickFilter?.distanceFilter}
|
cityValue={distanceQuickFilter?.distanceFilter}
|
||||||
quickValue={distanceQuickFilter?.order}
|
quickValue={distanceQuickFilter?.order}
|
||||||
|
districtValue={distanceQuickFilter?.district}
|
||||||
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -132,3 +132,16 @@ export const getCityQrCode = async () => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取行政区列表
|
||||||
|
export const getDistricts = async (params: { country: string; state: string }) => {
|
||||||
|
try {
|
||||||
|
// 调用HTTP服务获取行政区列表
|
||||||
|
return httpService.post('/cities/cities', params)
|
||||||
|
} catch (error) {
|
||||||
|
// 捕获并打印错误信息
|
||||||
|
console.error("行政区列表获取失败:", error);
|
||||||
|
// 抛出错误以便上层处理
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getGamesCount,
|
getGamesCount,
|
||||||
getCities,
|
getCities,
|
||||||
getCityQrCode,
|
getCityQrCode,
|
||||||
|
getDistricts,
|
||||||
} from "../services/listApi";
|
} from "../services/listApi";
|
||||||
import {
|
import {
|
||||||
ListActions,
|
ListActions,
|
||||||
@@ -20,12 +21,23 @@ import {
|
|||||||
function translateCityData(dataTree) {
|
function translateCityData(dataTree) {
|
||||||
return dataTree.map((item) => {
|
return dataTree.map((item) => {
|
||||||
const { children, ...rest } = item;
|
const { children, ...rest } = item;
|
||||||
|
// 只保留两级:国家和省份,去掉第三级(区域)
|
||||||
|
const processedChildren = children?.length > 0
|
||||||
|
? children.map(child => ({
|
||||||
|
...child,
|
||||||
|
text: child.name,
|
||||||
|
label: child.name,
|
||||||
|
value: child.name,
|
||||||
|
children: null, // 去掉第三级
|
||||||
|
}))
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
text: rest.name,
|
text: rest.name,
|
||||||
label: rest.name,
|
label: rest.name,
|
||||||
value: rest.name,
|
value: rest.name,
|
||||||
children: children?.length > 0 ? translateCityData(children) : null,
|
children: processedChildren,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -48,6 +60,7 @@ const defaultFilterOptions: IFilterOptions = {
|
|||||||
const defaultDistanceQuickFilter = {
|
const defaultDistanceQuickFilter = {
|
||||||
distanceFilter: "",
|
distanceFilter: "",
|
||||||
order: "0",
|
order: "0",
|
||||||
|
district: "", // 新增:行政区筛选
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultPageOption = {
|
const defaultPageOption = {
|
||||||
@@ -155,7 +168,8 @@ const commonStateDefaultValue = {
|
|||||||
],
|
],
|
||||||
cities: [],
|
cities: [],
|
||||||
cityQrCode: [],
|
cityQrCode: [],
|
||||||
area: ['', '', ''] as [string, string, string],
|
area: ['', ''] as [string, string], // 改为两级:国家、省份
|
||||||
|
districts: [], // 新增:行政区列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 store
|
// 创建 store
|
||||||
@@ -176,42 +190,45 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
const filterOptions = currentPageState?.filterOptions || {};
|
const filterOptions = currentPageState?.filterOptions || {};
|
||||||
// 全城和快捷筛选
|
// 全城和快捷筛选
|
||||||
const distanceQuickFilter = currentPageState?.distanceQuickFilter || {};
|
const distanceQuickFilter = currentPageState?.distanceQuickFilter || {};
|
||||||
const { distanceFilter, order } = distanceQuickFilter || {};
|
const { distanceFilter, order, district } = distanceQuickFilter || {};
|
||||||
|
|
||||||
// 从 area 中获取省份和城市名称(area 格式: ["中国", 省份, 城市])
|
// 从 area 中获取省份名称(area 格式: ["中国", 省份])
|
||||||
const province = state.area?.[1] || ""; // area[1] 是省份
|
const province = state.area?.[1] || ""; // area[1] 是省份
|
||||||
const city = state.area?.at(-1) || ""; // area[2] 是城市(虽然参数名是 city,但实际是城市名称)
|
|
||||||
|
// city 参数逻辑:
|
||||||
// 处理 dateRange,确保始终有两个值
|
// 1. 如果选择了行政区(district 有值),使用行政区的名称(label)
|
||||||
let dateRange: [string, string] = defaultDateRange;
|
// 2. 如果是"全城"(distanceFilter 为空),不传 city
|
||||||
const filterDateRange = filterOptions?.dateRange;
|
let city: string | undefined = undefined;
|
||||||
if (Array.isArray(filterDateRange)) {
|
if (district) {
|
||||||
if (filterDateRange.length === 0) {
|
// 从 districts 数组中查找对应的行政区名称
|
||||||
// 如果没有选择日期,使用默认值
|
const selectedDistrict = state.districts.find(item => item.value === district);
|
||||||
dateRange = defaultDateRange;
|
if (selectedDistrict) {
|
||||||
} else if (filterDateRange.length === 1) {
|
city = selectedDistrict.label; // 传递行政区名称,如"静安"
|
||||||
// 如果只选择了一个日期,转换为两个相同的值
|
|
||||||
const singleDate = filterDateRange[0] as string;
|
|
||||||
dateRange = [singleDate, singleDate];
|
|
||||||
} else if (filterDateRange.length >= 2) {
|
|
||||||
// 如果选择了两个或更多日期,取前两个
|
|
||||||
// 如果两个日期相同,保持两个相同的值
|
|
||||||
dateRange = [filterDateRange[0] as string, filterDateRange[1] as string];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 如果是"全城"(distanceFilter 为空),city 保持 undefined,不会被传递
|
||||||
|
|
||||||
|
// 使用 filterOptions 中的 dateRange
|
||||||
|
const dateRange: [string, string] = filterOptions?.dateRange || defaultDateRange;
|
||||||
|
|
||||||
|
const searchOption: any = {
|
||||||
|
...filterOptions,
|
||||||
|
title: state.searchValue,
|
||||||
|
ntrpMin: filterOptions?.ntrp?.[0],
|
||||||
|
ntrpMax: filterOptions?.ntrp?.[1],
|
||||||
|
dateRange: dateRange, // 确保始终是两个值的数组
|
||||||
|
distanceFilter: distanceFilter,
|
||||||
|
province: province, // 添加省份参数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只在有值时添加 city 参数
|
||||||
|
if (city) {
|
||||||
|
searchOption.city = city;
|
||||||
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
pageOption: currentPageState?.pageOption,
|
pageOption: currentPageState?.pageOption,
|
||||||
seachOption: {
|
seachOption: searchOption,
|
||||||
...filterOptions,
|
|
||||||
title: state.searchValue,
|
|
||||||
ntrpMin: filterOptions?.ntrp?.[0],
|
|
||||||
ntrpMax: filterOptions?.ntrp?.[1],
|
|
||||||
dateRange: dateRange, // 确保始终是两个值的数组
|
|
||||||
distanceFilter: distanceFilter,
|
|
||||||
province: province, // 添加省份参数
|
|
||||||
city: city, // 添加区县参数(虽然叫 city,但实际是区县)
|
|
||||||
},
|
|
||||||
order: order,
|
order: order,
|
||||||
lat: state?.location?.latitude,
|
lat: state?.location?.latitude,
|
||||||
lng: state?.location?.longitude,
|
lng: state?.location?.longitude,
|
||||||
@@ -618,7 +635,36 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
updateArea(payload: [string, string, string]) {
|
// 新增:获取行政区列表
|
||||||
|
async getDistricts() {
|
||||||
|
try {
|
||||||
|
const state = get();
|
||||||
|
// 从 area 中获取省份,area 格式: ["中国", 省份, 城市]
|
||||||
|
const country = "中国";
|
||||||
|
const province = state.area?.at(1) || "上海"; // area[1] 是省份
|
||||||
|
|
||||||
|
const res = await getDistricts({
|
||||||
|
country,
|
||||||
|
state: province
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
const districts = res.data.map((item) => ({
|
||||||
|
label: item.cn_city,
|
||||||
|
value: item.id.toString(),
|
||||||
|
id: item.id,
|
||||||
|
}));
|
||||||
|
set({ districts });
|
||||||
|
return districts;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取行政区列表失败:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateArea(payload: [string, string]) {
|
||||||
const state = get();
|
const state = get();
|
||||||
set({
|
set({
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface PageState {
|
|||||||
distanceQuickFilter: {
|
distanceQuickFilter: {
|
||||||
distanceFilter: string;
|
distanceFilter: string;
|
||||||
order: string;
|
order: string;
|
||||||
|
district?: string; // 新增:行政区筛选
|
||||||
};
|
};
|
||||||
filterCount: number;
|
filterCount: number;
|
||||||
pageOption: {
|
pageOption: {
|
||||||
@@ -89,7 +90,8 @@ export interface ListState {
|
|||||||
gamesNum: number;
|
gamesNum: number;
|
||||||
cities: CityTree[];
|
cities: CityTree[];
|
||||||
cityQrCode: CityQrCodeItem[];
|
cityQrCode: CityQrCodeItem[];
|
||||||
area: [string, string, string];
|
area: [string, string]; // 两级:国家、省份
|
||||||
|
districts: BubbleOption[]; // 新增:行政区列表
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListActions {
|
export interface ListActions {
|
||||||
@@ -119,7 +121,8 @@ export interface ListActions {
|
|||||||
updateDistanceQuickFilter: (payload: Record<string, any>) => void;
|
updateDistanceQuickFilter: (payload: Record<string, any>) => void;
|
||||||
getCities: () => Promise<void>;
|
getCities: () => Promise<void>;
|
||||||
getCityQrCode: () => Promise<void>;
|
getCityQrCode: () => Promise<void>;
|
||||||
updateArea: (payload: [string, string, string]) => void;
|
getDistricts: () => Promise<BubbleOption[]>; // 新增:获取行政区
|
||||||
|
updateArea: (payload: [string, string]) => void;
|
||||||
refreshBothLists: () => Promise<void>;
|
refreshBothLists: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user