Compare commits
12 Commits
feature/ju
...
b417b3a4c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b417b3a4c2 | ||
|
|
8d729a0132 | ||
|
|
2d68a558da | ||
|
|
ce0a299b59 | ||
|
|
ca4b52570f | ||
|
|
cff9afd1e8 | ||
|
|
d149de1f42 | ||
|
|
969066591c | ||
| ebb7116c25 | |||
| 73bb56b1b2 | |||
| 9cde3a606c | |||
|
|
ee579df162 |
@@ -148,4 +148,8 @@ src/
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
MIT
|
||||
|
||||
"appid": "wx915ecf6c01bea4ec",
|
||||
|
||||
"appid": "wx815b533167eb7b53",
|
||||
@@ -57,6 +57,7 @@
|
||||
"@tarojs/shared": "4.1.5",
|
||||
"@tarojs/taro": "4.1.5",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"qweather-icons": "^1.8.0",
|
||||
"react": "^18.0.0",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"projectname": "playBallTogether",
|
||||
"description": "playBallTogether",
|
||||
"appid": "wx915ecf6c01bea4ec",
|
||||
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"es6": true,
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
"useStaticServer": false,
|
||||
"useLanDebug": false,
|
||||
"showES6CompileOption": false,
|
||||
"compileHotReLoad": false,
|
||||
"compileHotReLoad": true,
|
||||
"checkInvalidKey": true,
|
||||
"ignoreDevUnusedFiles": true,
|
||||
"bigPackageSizeSupport": true
|
||||
"bigPackageSizeSupport": true,
|
||||
"useIsolateContext": true
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,10 @@
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #3c3c43;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap:4px;
|
||||
}
|
||||
|
||||
.distanceWrap {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { Menu } from "@nutui/nutui-react-taro";
|
||||
import { Image, View, ScrollView } from "@tarojs/components";
|
||||
import Taro from "@tarojs/taro";
|
||||
import img from "@/config/images";
|
||||
import Bubble from "../Bubble";
|
||||
import { useListState } from "@/store/listStore";
|
||||
import { useListState, useListStore } from "@/store/listStore";
|
||||
import { getCurrentLocation } from "@/utils/locationUtils";
|
||||
import { updateUserLocation } from "@/services/userService";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
import { useUserActions } from "@/store/userStore";
|
||||
import "./index.scss";
|
||||
|
||||
const DistanceQuickFilterV2 = (props) => {
|
||||
@@ -19,15 +24,19 @@ const DistanceQuickFilterV2 = (props) => {
|
||||
quickValue,
|
||||
districtValue, // 新增:行政区选中值
|
||||
onMenuVisibleChange, // 菜单展开/收起回调
|
||||
onRelocate, // 重新定位回调
|
||||
} = props;
|
||||
const cityRef = useRef(null);
|
||||
const quickRef = useRef(null);
|
||||
const [changePosition, setChangePosition] = useState<number[]>([]);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [keys, setKeys] = useState(0);
|
||||
const [keys, setKeys] = useState(0);
|
||||
const [isRelocating, setIsRelocating] = useState(false);
|
||||
// 从 store 获取当前城市信息
|
||||
const { area } = useListState();
|
||||
const currentCity = area?.at(-1) || ""; // 获取省份/城市名称
|
||||
const { updateState } = useGlobalState() || {};
|
||||
const { fetchUserInfo, updateCache } = useUserActions();
|
||||
|
||||
// 全城筛选显示的标题 - 如果选择了行政区,显示行政区名称
|
||||
const getCityTitle = () => {
|
||||
@@ -79,6 +88,64 @@ const DistanceQuickFilterV2 = (props) => {
|
||||
index === 1 && (quickRef.current as any)?.toggle(false);
|
||||
};
|
||||
|
||||
|
||||
// 重新获取当前位置,调用接口把位置传递后端
|
||||
const handleRelocate = async () => {
|
||||
if (isRelocating) return;
|
||||
|
||||
setIsRelocating(true);
|
||||
(Taro as any).showLoading({ title: '定位中...', mask: true });
|
||||
|
||||
try {
|
||||
// 获取当前位置
|
||||
const location = await getCurrentLocation();
|
||||
|
||||
if (location && location.latitude && location.longitude) {
|
||||
// 更新 store 中的位置信息
|
||||
updateState?.({ location });
|
||||
|
||||
// 调用接口把位置传递给后端,传递一个值代表强制更新
|
||||
const response = await updateUserLocation(location.latitude, location.longitude, true);
|
||||
|
||||
// 如果接口返回成功,重新调用用户信息接口来更新 USER_SELECTED_CITY
|
||||
if (response?.code === 0) {
|
||||
|
||||
// 删除 缓存
|
||||
(Taro as any).removeStorageSync("USER_SELECTED_CITY");
|
||||
|
||||
// 延时一下
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
// 先清除缓存和 area,确保使用最新的用户信息
|
||||
await updateCache( [ response.data.last_location_province, response.data.last_location_city ]);
|
||||
|
||||
}
|
||||
|
||||
(Taro as any).showToast({
|
||||
title: '定位成功',
|
||||
icon: 'success',
|
||||
duration: 1500,
|
||||
});
|
||||
|
||||
// 通知父组件位置已更新,可以刷新列表
|
||||
if (onRelocate) {
|
||||
onRelocate(location);
|
||||
}
|
||||
} else {
|
||||
throw new Error('获取位置信息失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('重新定位失败:', error);
|
||||
(Taro as any).showToast({
|
||||
title: error?.message || '定位失败,请检查定位权限',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
} finally {
|
||||
setIsRelocating(false);
|
||||
(Taro as any).hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听菜单状态变化,通知父组件
|
||||
useEffect(() => {
|
||||
onMenuVisibleChange?.(isMenuOpen);
|
||||
@@ -103,8 +170,11 @@ const DistanceQuickFilterV2 = (props) => {
|
||||
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
|
||||
>
|
||||
<div className="positionWrap">
|
||||
<p className="title">当前位置</p>
|
||||
<p className="cityName">{currentCity}</p>
|
||||
<p className="title">{currentCity}</p>
|
||||
<p className="cityName" onClick={handleRelocate}>
|
||||
<img src={img.ICON_RELOCATE} style={{ width: '12px', height: "12px" }} />
|
||||
<span>重新定位</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="distanceWrap">
|
||||
<Bubble
|
||||
|
||||
@@ -105,15 +105,15 @@ const HomeNavbar = (props: IProps) => {
|
||||
const userInfo = useUserInfo();
|
||||
// 使用用户详情接口中的 last_location 字段
|
||||
// USER_SELECTED_CITY 第二个值应该是省份/直辖市,不能是区
|
||||
const lastLocationProvince = (userInfo as any)?.last_location_province || "";
|
||||
const lastLocationCity = (userInfo as any)?.last_location_city || "";
|
||||
// 只使用省份/直辖市,不使用城市(城市可能是区)
|
||||
const detectedLocation = lastLocationProvince;
|
||||
const detectedLocation = lastLocationCity;
|
||||
|
||||
// 检查是否应该显示定位确认弹窗
|
||||
const should_show_location_dialog = (): boolean => {
|
||||
try {
|
||||
const current_time = Date.now();
|
||||
|
||||
|
||||
// 检查是否在2小时内切换过城市
|
||||
const city_change_time = (Taro as any).getStorageSync(CITY_CHANGE_TIME_KEY);
|
||||
if (city_change_time) {
|
||||
@@ -127,13 +127,13 @@ const HomeNavbar = (props: IProps) => {
|
||||
(Taro as any).removeStorageSync(CITY_CHANGE_TIME_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查是否在2小时内已选择"继续浏览"
|
||||
const dismiss_time = (Taro as any).getStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY);
|
||||
if (!dismiss_time) {
|
||||
return true; // 没有记录,可以显示
|
||||
}
|
||||
|
||||
|
||||
const time_diff = current_time - dismiss_time;
|
||||
// 如果距离上次选择"继续浏览"已超过2小时,可以再次显示
|
||||
if (time_diff >= TWO_HOURS_MS) {
|
||||
@@ -141,7 +141,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
(Taro as any).removeStorageSync(LOCATION_DIALOG_DISMISS_TIME_KEY);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 在2小时内,不显示弹窗
|
||||
console.log(`[HomeNavbar] 距离上次选择"继续浏览"还不到2小时,剩余时间: ${Math.ceil((TWO_HOURS_MS - time_diff) / 1000 / 60)}分钟`);
|
||||
return false;
|
||||
@@ -158,7 +158,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
console.log('[HomeNavbar] 用户在2小时内已选择"继续浏览"或切换过城市,不显示弹窗');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('[HomeNavbar] 准备显示定位确认弹窗,隐藏 GuideBar');
|
||||
setLocationDialogData({ detectedProvince: detectedLocation, cachedCity });
|
||||
setLocationDialogVisible(true);
|
||||
@@ -172,13 +172,13 @@ const HomeNavbar = (props: IProps) => {
|
||||
useEffect(() => {
|
||||
// 1. 优先尝试从缓存中读取上次的定位信息
|
||||
const cachedCity = (Taro as any).getStorageSync(CITY_CACHE_KEY);
|
||||
|
||||
|
||||
if (cachedCity && Array.isArray(cachedCity) && cachedCity.length === 2) {
|
||||
// 如果有缓存的定位信息,使用缓存
|
||||
const cachedCityArray = cachedCity as [string, string];
|
||||
console.log("[HomeNavbar] 使用缓存的定位城市:", cachedCityArray);
|
||||
updateArea(cachedCityArray);
|
||||
|
||||
|
||||
// 如果用户详情中有位置信息,且与缓存不一致,检查是否需要弹窗
|
||||
if (detectedLocation && cachedCityArray[1] !== detectedLocation) {
|
||||
// 检查时间缓存,如果没有或过期,则弹出选择框
|
||||
@@ -192,7 +192,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
} else if (detectedLocation) {
|
||||
// 只有在完全没有缓存的情况下,才使用用户详情中的位置信息
|
||||
console.log("[HomeNavbar] 没有缓存,使用用户详情中的位置信息:", detectedLocation);
|
||||
const newArea: [string, string] = ["中国", detectedLocation];
|
||||
const newArea: [string, string] = [(userInfo as any)?.last_location_province || "", detectedLocation];
|
||||
updateArea(newArea);
|
||||
// 保存定位信息到缓存
|
||||
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
||||
@@ -263,10 +263,10 @@ const HomeNavbar = (props: IProps) => {
|
||||
// 处理定位弹窗确认
|
||||
const handleLocationDialogConfirm = () => {
|
||||
if (!locationDialogData) return;
|
||||
|
||||
|
||||
const { detectedProvince } = locationDialogData;
|
||||
// 用户选择"切换到",使用用户详情中的位置信息
|
||||
const newArea: [string, string] = ["中国", detectedProvince];
|
||||
const newArea: [string, string] = [(userInfo as any)?.last_location_province || "", detectedProvince];
|
||||
updateArea(newArea);
|
||||
// 更新缓存为新的定位信息
|
||||
(Taro as any).setStorageSync(CITY_CACHE_KEY, newArea);
|
||||
@@ -279,13 +279,13 @@ const HomeNavbar = (props: IProps) => {
|
||||
console.error('保存城市切换时间失败:', error);
|
||||
}
|
||||
console.log("切换到用户详情中的位置信息并更新缓存:", detectedProvince);
|
||||
|
||||
|
||||
// 关闭弹窗
|
||||
setLocationDialogVisible(false);
|
||||
setLocationDialogData(null);
|
||||
// 关闭弹窗时显示 GuideBar
|
||||
setShowGuideBar(true);
|
||||
|
||||
|
||||
// 刷新数据
|
||||
handleCityChangeWithoutCache();
|
||||
};
|
||||
@@ -293,11 +293,11 @@ const HomeNavbar = (props: IProps) => {
|
||||
// 处理定位弹窗取消(用户选择"继续浏览")
|
||||
const handleLocationDialogCancel = () => {
|
||||
if (!locationDialogData) return;
|
||||
|
||||
|
||||
const { cachedCity } = locationDialogData;
|
||||
// 用户选择"继续浏览",保持缓存的定位城市
|
||||
console.log("保持缓存的定位城市:", cachedCity[1]);
|
||||
|
||||
|
||||
// 记录用户选择"继续浏览"的时间戳,2小时内不再提示
|
||||
try {
|
||||
const current_time = Date.now();
|
||||
@@ -306,7 +306,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
} catch (error) {
|
||||
console.error('保存定位弹窗关闭时间失败:', error);
|
||||
}
|
||||
|
||||
|
||||
// 关闭弹窗
|
||||
setLocationDialogVisible(false);
|
||||
setLocationDialogData(null);
|
||||
@@ -321,7 +321,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
if (cityPopupVisible) {
|
||||
setCityPopupVisible(false);
|
||||
}
|
||||
|
||||
|
||||
const currentPagePath = getCurrentFullPath();
|
||||
if (currentPagePath === "/game_pages/searchResult/index") {
|
||||
(Taro as any).navigateBack();
|
||||
@@ -338,7 +338,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
if (cityPopupVisible) {
|
||||
setCityPopupVisible(false);
|
||||
}
|
||||
|
||||
|
||||
// 如果当前在列表页,点击后页面回到顶部
|
||||
if (getCurrentFullPath() === "/main_pages/index") {
|
||||
// 使用父组件传递的滚动方法(适配 ScrollView)
|
||||
@@ -363,7 +363,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
if (cityPopupVisible) {
|
||||
setCityPopupVisible(false);
|
||||
}
|
||||
|
||||
|
||||
if (leftIconClick) {
|
||||
leftIconClick();
|
||||
} else {
|
||||
@@ -397,10 +397,10 @@ const HomeNavbar = (props: IProps) => {
|
||||
const handleCityChange = async (_newArea: any) => {
|
||||
// 用户手动选择的城市保存到缓存
|
||||
console.log("用户手动选择城市,更新缓存:", _newArea);
|
||||
|
||||
|
||||
// 先更新 area 状态(用于界面显示和接口参数)
|
||||
updateArea(_newArea);
|
||||
|
||||
|
||||
// 保存城市到缓存
|
||||
try {
|
||||
(Taro as any).setStorageSync(CITY_CACHE_KEY, _newArea);
|
||||
@@ -411,7 +411,7 @@ const HomeNavbar = (props: IProps) => {
|
||||
} catch (error) {
|
||||
console.error("保存城市缓存失败:", error);
|
||||
}
|
||||
|
||||
|
||||
// 先调用列表接口(会使用更新后的 state.area)
|
||||
if (refreshBothLists) {
|
||||
await refreshBothLists();
|
||||
@@ -481,9 +481,8 @@ const HomeNavbar = (props: IProps) => {
|
||||
{/* 搜索导航 */}
|
||||
{!showTitle && (
|
||||
<View
|
||||
className={`inputCustomerNavbarContainer toggleElement secondElement hidden ${
|
||||
showInput && "visible"
|
||||
} ${showInput ? "inputCustomerNavbarShowInput" : ""}`}
|
||||
className={`inputCustomerNavbarContainer toggleElement secondElement hidden ${showInput && "visible"
|
||||
} ${showInput ? "inputCustomerNavbarShowInput" : ""}`}
|
||||
style={navbarStyle}
|
||||
>
|
||||
<View className="navContent">
|
||||
|
||||
@@ -4,10 +4,39 @@
|
||||
box-sizing: border-box;
|
||||
border-radius: 20px;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
background: linear-gradient(180deg, #BFFFEF 0%, #F2FFFC 100%), var(--Backgrounds-Primary, #FFF);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
background:
|
||||
linear-gradient(180deg, #bfffef 0%, #f2fffc 100%),
|
||||
var(--Backgrounds-Primary, #fff);
|
||||
|
||||
.lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 20px;
|
||||
z-index: 1;
|
||||
background-position-y: 85%;
|
||||
pointer-events: none;
|
||||
}
|
||||
// .gradient {
|
||||
// inset: 0;
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// z-index: -2;
|
||||
// border-radius: 20px;
|
||||
// background:
|
||||
// linear-gradient(180deg, #bfffef 0%, #f2fffc 100%),
|
||||
// var(--Backgrounds-Primary, #fff);
|
||||
// pointer-events: none;
|
||||
// }
|
||||
}
|
||||
|
||||
.higher {
|
||||
@@ -18,8 +47,6 @@
|
||||
.lower {
|
||||
height: 80px;
|
||||
@include commonCardStyle();
|
||||
|
||||
|
||||
}
|
||||
|
||||
.desc {
|
||||
@@ -30,7 +57,7 @@
|
||||
gap: 7px;
|
||||
|
||||
.title {
|
||||
color: #2A4D44;
|
||||
color: #2a4d44;
|
||||
font-family: "Noto Sans SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -38,7 +65,7 @@
|
||||
line-height: 24px;
|
||||
|
||||
.colorTip {
|
||||
color: #00E5AD;
|
||||
color: #00e5ad;
|
||||
font-family: "Noto Sans SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -47,7 +74,7 @@
|
||||
}
|
||||
|
||||
.strongTip {
|
||||
color: #00E5AD;
|
||||
color: #00e5ad;
|
||||
font-family: "Noto Sans SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
@@ -68,8 +95,10 @@
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
color: #5CA693;
|
||||
font-feature-settings: 'liga' off, 'clig' off;
|
||||
color: #5ca693;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
@@ -94,7 +123,9 @@
|
||||
border-radius: 50%;
|
||||
border: 1px solid #efefef;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 8px 20px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow:
|
||||
0 0 1px 0 rgba(0, 0, 0, 0.2),
|
||||
0 8px 20px 0 rgba(0, 0, 0, 0.12);
|
||||
|
||||
.avatarUrl {
|
||||
width: calc(90px * $multiple);
|
||||
@@ -112,8 +143,14 @@
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: calc(20px * $multiple);
|
||||
border: 4px solid #FFF;
|
||||
background: linear-gradient(0deg, rgba(89, 255, 214, 0.20) 0%, rgba(89, 255, 214, 0.20) 100%), #FFF;
|
||||
border: 4px solid #fff;
|
||||
background:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
rgba(89, 255, 214, 0.2) 0%,
|
||||
rgba(89, 255, 214, 0.2) 100%
|
||||
),
|
||||
#fff;
|
||||
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -135,4 +172,4 @@
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@include commonAvatarStyle(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@ import React, { useState, useEffect, useCallback, memo } from "react";
|
||||
import { View, Image, Text } from "@tarojs/components";
|
||||
import { requireLoginWithPhone } from "@/utils/helper";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
||||
import {
|
||||
useUserInfo,
|
||||
useUserActions,
|
||||
useLastTestResult,
|
||||
} from "@/store/userStore";
|
||||
// import { getCurrentFullPath } from "@/utils";
|
||||
import { OSS_BASE_URL } from "@/config/api";
|
||||
import { StageType } from "@/services/evaluateService";
|
||||
import { waitForAuthInit } from "@/utils/authInit";
|
||||
import DocCopy from "@/static/ntrp/ntrp_doc_copy.svg";
|
||||
@@ -26,8 +31,6 @@ function NTRPTestEntryCard(props: {
|
||||
// 使用全局状态中的测试结果,避免重复调用接口
|
||||
const lastTestResult = useLastTestResult();
|
||||
|
||||
console.log(userInfo);
|
||||
|
||||
// 从全局状态中获取测试结果,如果不存在则调用接口(使用请求锁避免重复调用)
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
@@ -121,7 +124,7 @@ function NTRPTestEntryCard(props: {
|
||||
if (!testFlag && !userInfo.phone) {
|
||||
Taro.navigateTo({
|
||||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||||
`/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`
|
||||
`/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
|
||||
)}`,
|
||||
});
|
||||
return false;
|
||||
@@ -132,7 +135,7 @@ function NTRPTestEntryCard(props: {
|
||||
}`,
|
||||
});
|
||||
},
|
||||
[setCallback, testFlag, type, evaluateCallback, userInfo.phone]
|
||||
[setCallback, testFlag, type, evaluateCallback, userInfo.phone],
|
||||
);
|
||||
|
||||
// 如果最近一个月有测试记录,则不展示
|
||||
@@ -142,6 +145,12 @@ function NTRPTestEntryCard(props: {
|
||||
|
||||
return type === EvaluateScene.list ? (
|
||||
<View className={styles.higher} onClick={handleTest}>
|
||||
<View
|
||||
className={styles.lines}
|
||||
style={{
|
||||
backgroundImage: `url(${OSS_BASE_URL}/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||
}}
|
||||
/>
|
||||
<View className={styles.desc}>
|
||||
<View>
|
||||
<View className={styles.title}>
|
||||
@@ -176,6 +185,12 @@ function NTRPTestEntryCard(props: {
|
||||
</View>
|
||||
) : (
|
||||
<View className={styles.lower} onClick={handleTest}>
|
||||
<View
|
||||
className={styles.lines}
|
||||
style={{
|
||||
backgroundImage: `url(${OSS_BASE_URL}/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||||
}}
|
||||
/>
|
||||
<View className={styles.desc}>
|
||||
<View className={styles.title}>
|
||||
<Text>不知道自己的</Text>
|
||||
|
||||
@@ -67,7 +67,7 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
||||
};
|
||||
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
||||
const [_, address] = area;
|
||||
if (address !== '上海') {
|
||||
if (address !== '上海市') {
|
||||
(Taro as any).showModal({
|
||||
title: '提示',
|
||||
content: '仅上海地区开放,您可加入社群或切换城市',
|
||||
|
||||
@@ -45,7 +45,7 @@ const SearchBarComponent = (props: IProps) => {
|
||||
</View>
|
||||
}
|
||||
className={styles.searchBar}
|
||||
placeholder="搜索上海的球局和场地"
|
||||
placeholder="搜索球局和场地"
|
||||
onChange={handleChange}
|
||||
value={value}
|
||||
onInputClick={onInputClick}
|
||||
|
||||
@@ -70,4 +70,5 @@ export default {
|
||||
ICON_LIST_NTPR: require('@/static/list/ntpr.svg'),
|
||||
ICON_LIST_CHANGDA: require('@/static/list/icon-changda.svg'),
|
||||
ICON_LIST_CHANGDA_QIuju: require('@/static/list/changdaqiuju.png'),
|
||||
ICON_RELOCATE: require('@/static/list/icon-relocate.svg'),
|
||||
}
|
||||
|
||||
@@ -161,29 +161,30 @@ const ListContainer = (props) => {
|
||||
|
||||
// 渲染 banner 卡片
|
||||
const renderBanner = (item, index) => {
|
||||
if (!item?.banner_image_url) return null;
|
||||
if (!item?.banner_image_url) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View
|
||||
key={item.id || `banner-${index}`}
|
||||
onClick={() => {
|
||||
const target = item.banner_detail_url;
|
||||
if (target) {
|
||||
(Taro as any).navigateTo({
|
||||
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(target)}`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
maxHeight: "122px",
|
||||
height: "122px",
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
backgroundImage: `url(${item.banner_image_url})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={item.banner_image_url}
|
||||
mode="widthFix"
|
||||
style={{ width: "100%", display: "block", maxHeight: "122px" }}
|
||||
onClick={() => {
|
||||
const target = item.banner_detail_url;
|
||||
if (target) {
|
||||
(Taro as any).navigateTo({
|
||||
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(target)}`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -216,7 +217,7 @@ const ListContainer = (props) => {
|
||||
}
|
||||
if (match.type === "evaluateCard") {
|
||||
return (
|
||||
<NTRPTestEntryCard key="evaluate" type={EvaluateScene.list} />
|
||||
<NTRPTestEntryCard key={`evaluate-${index}`} type={EvaluateScene.list} />
|
||||
);
|
||||
}
|
||||
return <ListCard key={match?.id || index} {...match} />;
|
||||
|
||||
@@ -131,7 +131,7 @@ const ListSearch = () => {
|
||||
<View className="topSearch">
|
||||
<Image className="searchIcon" src={img.ICON_LIST_SEARCH_SEARCH} />
|
||||
<Input
|
||||
placeholder="搜索上海的球局和场地"
|
||||
placeholder="搜索球局和场地"
|
||||
value={searchValue}
|
||||
defaultValue={searchValue}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -62,6 +62,7 @@ function SharePoster(props) {
|
||||
const qrCodeUrl = await base64ToTempFilePath(
|
||||
qrCodeUrlRes.data.qr_code_base64
|
||||
);
|
||||
debugger
|
||||
await delay(100);
|
||||
const url = await generatePosterImage({
|
||||
playType: play_type,
|
||||
|
||||
@@ -193,7 +193,7 @@ const LoginPage: React.FC = () => {
|
||||
/>
|
||||
</View>
|
||||
<Text className="button_text">
|
||||
{is_loading ? "登录中..." : "微信授权登录"}
|
||||
{is_loading ? "登录中..." : "一键登录"}
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
@@ -208,7 +208,7 @@ const LoginPage: React.FC = () => {
|
||||
src={require("@/static/login/phone_icon.svg")}
|
||||
/>
|
||||
</View>
|
||||
<Text className="button_text">手机号验证码登录</Text>
|
||||
<Text className="button_text">手机号快捷登录</Text>
|
||||
</Button>
|
||||
|
||||
{/* 用户协议复选框 */}
|
||||
@@ -224,13 +224,13 @@ const LoginPage: React.FC = () => {
|
||||
className="terms_link"
|
||||
onClick={() => handle_view_terms("terms")}
|
||||
>
|
||||
《开场的条款和条件》
|
||||
《有场的条款和条件》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_link"
|
||||
onClick={() => handle_view_terms("binding")}
|
||||
>
|
||||
《开场与微信号绑定协议》
|
||||
《有场与微信号绑定协议》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_link"
|
||||
@@ -259,13 +259,13 @@ const LoginPage: React.FC = () => {
|
||||
className="terms_item"
|
||||
onClick={() => handle_view_terms("terms")}
|
||||
>
|
||||
《开场的条款和条件》
|
||||
《有场的条款和条件》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_item"
|
||||
onClick={() => handle_view_terms("binding")}
|
||||
>
|
||||
《开场与微信号绑定协议》
|
||||
《有场与微信号绑定协议》
|
||||
</Text>
|
||||
<Text
|
||||
className="terms_item"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 条款页面 - 开场的条款和条件
|
||||
# 条款页面 - 有场的条款和条件
|
||||
|
||||
## 功能概述
|
||||
|
||||
条款页面展示完整的《开场的条款和条件》内容,用户需要仔细阅读并同意后才能继续使用平台服务。
|
||||
条款页面展示完整的《有场的条款和条件》内容,用户需要仔细阅读并同意后才能继续使用平台服务。
|
||||
|
||||
## 🎨 设计特点
|
||||
|
||||
@@ -54,7 +54,7 @@ TermsPage
|
||||
|
||||
## 📋 条款内容
|
||||
|
||||
本页面包含完整的《开场的条款和条件》,涵盖以下十个主要部分:
|
||||
本页面包含完整的《有场的条款和条件》,涵盖以下十个主要部分:
|
||||
|
||||
### 1. 服务内容
|
||||
- 活动发布、报名、聊天室沟通、活动提醒等服务
|
||||
|
||||
@@ -7,7 +7,7 @@ const TermsPage: React.FC = () => {
|
||||
// 获取页面参数
|
||||
const [termsType, setTermsType] = React.useState('terms');
|
||||
const [pageTitle, setPageTitle] = React.useState('条款和条件');
|
||||
const [termsTitle, setTermsTitle] = React.useState('《开场的条款和条件》');
|
||||
const [termsTitle, setTermsTitle] = React.useState('《有场的条款和条件》');
|
||||
const [termsContent, setTermsContent] = React.useState('');
|
||||
|
||||
// 返回上一页
|
||||
@@ -23,7 +23,7 @@ const TermsPage: React.FC = () => {
|
||||
switch (type) {
|
||||
case 'terms':
|
||||
setPageTitle('条款和条件');
|
||||
setTermsTitle('《开场的条款和条件》');
|
||||
setTermsTitle('《有场的条款和条件》');
|
||||
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。</span>
|
||||
|
||||
一、服务内容
|
||||
@@ -69,7 +69,7 @@ const TermsPage: React.FC = () => {
|
||||
break;
|
||||
case 'binding':
|
||||
setPageTitle('微信号绑定协议');
|
||||
setTermsTitle('《开场与微信号绑定协议》');
|
||||
setTermsTitle('《有场与微信号绑定协议》');
|
||||
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。</span>
|
||||
|
||||
一、绑定服务说明
|
||||
@@ -171,7 +171,7 @@ const TermsPage: React.FC = () => {
|
||||
break;
|
||||
default:
|
||||
setPageTitle('条款和条件');
|
||||
setTermsTitle('《开场的条款和条件》');
|
||||
setTermsTitle('《有场的条款和条件》');
|
||||
setTermsContent('条款内容加载中...');
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -64,7 +64,7 @@ VerificationPage
|
||||
- **页面跳转**:登录成功后跳转到首页
|
||||
|
||||
### 协议支持
|
||||
- **条款链接**:《开场的条款和条件》
|
||||
- **条款链接**:《有场的条款和条件》
|
||||
- **隐私政策**:《隐私权政策》
|
||||
- **动态跳转**:支持通过 URL 参数指定协议类型
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
gamesNum, // 新增:获取球局数量
|
||||
} = store;
|
||||
|
||||
const supportedCitiesList = useDictionaryStore((s) => s.getDictionaryValue('supported_cities', ['上海市'])) || [];
|
||||
|
||||
const {
|
||||
isShowFilterPopup,
|
||||
data: matches,
|
||||
@@ -92,6 +94,8 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
// 记录上一次加载数据时的城市,用于检测城市变化
|
||||
const lastLoadedAreaRef = useRef<[string, string] | null>(null);
|
||||
const prevIsActiveRef = useRef(isActive);
|
||||
// 记录是否是进入列表页的第一次调用 updateUserLocation(首次传 force: true)
|
||||
const hasUpdatedLocationRef = useRef(false);
|
||||
|
||||
// 处理距离筛选显示/隐藏
|
||||
const handleDistanceFilterVisibleChange = useCallback(
|
||||
@@ -289,9 +293,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
currentProvince,
|
||||
});
|
||||
|
||||
// 地址发生变化或不一致,重新加载数据和球局数量
|
||||
// 先调用列表接口,然后在列表接口完成后调用数量接口
|
||||
(async () => {
|
||||
// 延迟刷新,等 tab 切换动画完成后再加载,避免切换时列表重渲染导致抖动
|
||||
const delayMs = 280;
|
||||
const timer = setTimeout(async () => {
|
||||
try {
|
||||
if (refreshBothLists) {
|
||||
await refreshBothLists();
|
||||
@@ -307,7 +311,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
} catch (error) {
|
||||
console.error("重新加载数据失败:", error);
|
||||
}
|
||||
})();
|
||||
}, delayMs);
|
||||
prevIsActiveRef.current = isActive;
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +370,10 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
updateState({ location });
|
||||
if (location && location.latitude && location.longitude) {
|
||||
try {
|
||||
await updateUserLocation(location.latitude, location.longitude);
|
||||
// 进入列表页的第一次调用传 force: true,后续调用传 false
|
||||
const isFirstCall = !hasUpdatedLocationRef.current;
|
||||
await updateUserLocation(location.latitude, location.longitude, isFirstCall);
|
||||
hasUpdatedLocationRef.current = true;
|
||||
} catch (error) {
|
||||
console.error("更新用户位置失败:", error);
|
||||
}
|
||||
@@ -446,6 +455,17 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
// 处理重新定位
|
||||
const handleRelocate = async (location) => {
|
||||
try {
|
||||
// 位置已更新到后端,刷新列表数据
|
||||
await getMatchesData();
|
||||
await fetchGetGamesCount();
|
||||
} catch (error) {
|
||||
console.error("刷新列表失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchClick = () => {
|
||||
navigateTo({
|
||||
url: "/game_pages/search/index",
|
||||
@@ -465,7 +485,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
initDictionaryData();
|
||||
}, []);
|
||||
|
||||
// 获取省份名称(area 格式: ["中国", "省份"])
|
||||
|
||||
const province = area?.at(1) || "上海";
|
||||
|
||||
function renderCityQrcode() {
|
||||
@@ -507,8 +527,12 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
}
|
||||
|
||||
// 判定是否显示"暂无球局"页面
|
||||
// 条件:省份不是上海 或 (已加载完成且球局数量为0)
|
||||
const shouldShowNoGames = province !== "上海";
|
||||
// 从配置接口 /parameter/many_key 获取 supported_cities(格式如 "上海市||北京市")
|
||||
// 当前省份在有球局城市列表中则显示列表,否则显示暂无球局
|
||||
const shouldShowNoGames =
|
||||
supportedCitiesList.length > 0
|
||||
? !supportedCitiesList.includes(province)
|
||||
: province !== "上海市"; // 配置未加载时默认按上海判断
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -559,6 +583,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
quickValue={distanceQuickFilter?.order}
|
||||
districtValue={distanceQuickFilter?.district}
|
||||
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
||||
onRelocate={handleRelocate}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -21,21 +21,17 @@
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
transition: opacity 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
||||
transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
transition: opacity 0.25s ease-out;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
pointer-events: none;
|
||||
will-change: opacity, transform;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
visibility: hidden;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
z-index: 1;
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
.banner_detail_page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.banner_detail_content {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.banner_detail_image {
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: calc(100vh - 98px);
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -163,7 +163,6 @@
|
||||
|
||||
&__qr_image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__qr_placeholder {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback, forwardRef, useImperativeHandle, useEffect } from 'react'
|
||||
import React, { useState, useCallback, forwardRef, useImperativeHandle } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||||
import images from '@/config/images'
|
||||
@@ -70,6 +70,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
onAnyInput
|
||||
}, ref) => {
|
||||
const [openPicker, setOpenPicker] = useState(false);
|
||||
const [scrollTop, setScrollTop] = useState(0);
|
||||
const { getDictionaryValue } = useDictionaryActions()
|
||||
const court_type = getDictionaryValue('court_type') || []
|
||||
const court_surface = getDictionaryValue('court_surface') || []
|
||||
@@ -170,14 +171,27 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
|
||||
|
||||
|
||||
const changeTextarea = (value) => {
|
||||
if (value) {
|
||||
// 先滚动到底部
|
||||
setScrollTop(scrollTop ? scrollTop + 1 : 9999);
|
||||
// 使用 setTimeout 确保滚动后再更新 openPicker
|
||||
}
|
||||
}
|
||||
|
||||
const changePicker = (value) => {
|
||||
setOpenPicker(value)
|
||||
setOpenPicker(value);
|
||||
}
|
||||
|
||||
console.log(stadium,'stadiumstadium');
|
||||
return (
|
||||
<View className='stadium-detail'>
|
||||
<ScrollView className='stadium-detail-scroll' refresherBackground="#FAFAFA" scrollY={!openPicker}>
|
||||
<ScrollView
|
||||
className='stadium-detail-scroll'
|
||||
refresherBackground="#FAFAFA"
|
||||
scrollY={!openPicker}
|
||||
scrollTop={scrollTop}
|
||||
>
|
||||
{/* 已选球场 */}
|
||||
<View
|
||||
className={`stadium-item`}
|
||||
@@ -220,9 +234,12 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
<View className='textarea-tag-container'>
|
||||
<TextareaTag
|
||||
value={formData[item.prop]}
|
||||
onChange={(value) => updateFormData(item.prop, value)}
|
||||
onBlur={() => changePicker(false)}
|
||||
onFocus={() => changePicker(true)}
|
||||
onChange={(value) => {
|
||||
changeTextarea(true)
|
||||
updateFormData(item.prop, value)
|
||||
}}
|
||||
// onBlur={() => changeTextarea(false)}
|
||||
onFocus={() => changeTextarea(true)}
|
||||
placeholder='有其他场地信息可备注'
|
||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||
/>
|
||||
|
||||
@@ -134,7 +134,7 @@ export const getCityQrCode = async () => {
|
||||
}
|
||||
|
||||
// 获取行政区列表
|
||||
export const getDistricts = async (params: { country: string; state: string }) => {
|
||||
export const getDistricts = async (params: { province: string; city: string }) => {
|
||||
try {
|
||||
// 调用HTTP服务获取行政区列表
|
||||
return httpService.post('/cities/cities', params)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UserInfo } from "@/components/UserInfo";
|
||||
import { API_CONFIG } from "@/config/api";
|
||||
import httpService, { ApiResponse } from "./httpService";
|
||||
import uploadFiles from "./uploadFiles";
|
||||
import Taro from "@tarojs/taro";
|
||||
import * as Taro from "@tarojs/taro";
|
||||
import getCurrentConfig from "@/config/env";
|
||||
import { clear_login_state } from "@/services/loginService";
|
||||
|
||||
@@ -740,12 +740,14 @@ export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
|
||||
// 更新用户坐标位置
|
||||
export const updateUserLocation = async (
|
||||
latitude: number,
|
||||
longitude: number
|
||||
longitude: number,
|
||||
force: boolean = false
|
||||
) => {
|
||||
try {
|
||||
const response = await httpService.post("/user/update_location", {
|
||||
latitude,
|
||||
longitude,
|
||||
force
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
||||
10
src/static/list/icon-relocate.svg
Normal file
10
src/static/list/icon-relocate.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_8289_53268)">
|
||||
<path d="M10.9775 4.82657H8.04862C7.97102 4.82657 7.89658 4.7958 7.84163 4.74101C7.78668 4.68622 7.7557 4.61188 7.75547 4.53428V4.36457C7.75497 4.32525 7.76254 4.28625 7.77773 4.24998C7.79292 4.21371 7.81539 4.18094 7.84376 4.15371L8.88604 3.11143C8.50894 2.72825 8.05949 2.4238 7.56377 2.21574C7.06805 2.00768 6.53594 1.90016 5.99833 1.89943C5.1996 1.90138 4.41883 2.13662 3.75196 2.57623C3.08509 3.01584 2.56116 3.64067 2.24453 4.37397C1.92791 5.10727 1.83238 5.91708 1.9697 6.70392C2.10701 7.49077 2.47118 8.22036 3.01746 8.80307C3.56375 9.38578 4.26835 9.79622 5.0447 9.98397C5.82106 10.1717 6.63535 10.1286 7.38753 9.8599C8.13971 9.59121 8.79702 9.10864 9.2787 8.47149C9.76039 7.83435 10.0455 7.07037 10.0989 6.27343C10.1075 6.11828 10.236 5.99743 10.3912 5.99743H10.9775C11.0574 6.00001 11.1331 6.03387 11.1883 6.09171C11.2415 6.15 11.2698 6.22885 11.2638 6.30771C11.1937 7.51221 10.7124 8.6562 9.90046 9.54861C9.08847 10.441 7.99488 11.0278 6.80233 11.211C5.60978 11.3942 4.39049 11.1627 3.34808 10.5551C2.30568 9.94759 1.50328 9.00078 1.0749 7.87285C0.646012 6.74568 0.616794 5.50548 0.992127 4.35935C1.36746 3.21323 2.12463 2.23056 3.13719 1.57543C4.15001 0.920266 5.35687 0.632222 6.55641 0.759351C7.75595 0.88648 8.87562 1.42109 9.72862 2.274L10.6012 1.40143C10.6279 1.37384 10.6599 1.35189 10.6952 1.33687C10.7305 1.32186 10.7685 1.31408 10.8069 1.314H10.9766C11.0541 1.31422 11.1283 1.34509 11.183 1.39986C11.2378 1.45462 11.2687 1.52883 11.2689 1.60628V4.53428C11.2687 4.61173 11.2378 4.68595 11.183 4.74071C11.1283 4.79548 11.0549 4.82634 10.9775 4.82657Z" fill="#A6A6A6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8289_53268">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -36,14 +36,17 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
||||
set({ isLoading: true, error: null })
|
||||
|
||||
try {
|
||||
const keys = 'publishing_requirements,court_type,court_surface,supplementary_information,game_play,fabu_tip';
|
||||
const keys = 'publishing_requirements,court_type,court_surface,supplementary_information,game_play,fabu_tip,supported_cities';
|
||||
const response = await commonApi.getDictionaryManyKey(keys)
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
const dictionaryData = {};
|
||||
keys.split(',').forEach(key => {
|
||||
const list = response.data[key];
|
||||
const listData = list.split('|');
|
||||
// supported_cities 格式如 "上海市||北京市",用 || 分割
|
||||
const listData = key === 'supported_cities'
|
||||
? (list ? String(list).split('||').map((s) => s.trim()).filter(Boolean) : [])
|
||||
: (list ? list.split('|') : []);
|
||||
dictionaryData[key] = listData;
|
||||
})
|
||||
set({
|
||||
|
||||
@@ -44,16 +44,16 @@ function translateCityData(dataTree) {
|
||||
return dataTree.map((item) => {
|
||||
const { children, ...rest } = item;
|
||||
// 只保留两级:国家和省份,去掉第三级(区域)
|
||||
const processedChildren = children?.length > 0
|
||||
const processedChildren = children?.length > 0
|
||||
? children.map(child => ({
|
||||
...child,
|
||||
text: child.name,
|
||||
label: child.name,
|
||||
value: child.name,
|
||||
children: null, // 去掉第三级
|
||||
}))
|
||||
...child,
|
||||
text: child.name,
|
||||
label: child.name,
|
||||
value: child.name,
|
||||
children: null, // 去掉第三级
|
||||
}))
|
||||
: null;
|
||||
|
||||
|
||||
return {
|
||||
...rest,
|
||||
text: rest.name,
|
||||
@@ -214,20 +214,19 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
// 全城和快捷筛选
|
||||
const distanceQuickFilter = currentPageState?.distanceQuickFilter || {};
|
||||
const { distanceFilter, order, district } = distanceQuickFilter || {};
|
||||
|
||||
|
||||
// 始终使用 state.area,确保所有接口使用一致的城市参数
|
||||
const areaProvince = state.area?.at(1) || "";
|
||||
const areaProvince = state.area?.at(0) || "";
|
||||
const areaCity = state.area?.at(1) || "";
|
||||
const last_location_province = areaProvince;
|
||||
|
||||
// city 参数逻辑:
|
||||
// 1. 如果选择了行政区(district 有值),使用行政区的名称(label)
|
||||
// 2. 如果是"全城"(distanceFilter 为空),不传 city
|
||||
let city: string | undefined = undefined;
|
||||
|
||||
|
||||
let county: string | undefined = undefined;
|
||||
if (district) {
|
||||
// 从 districts 数组中查找对应的行政区名称
|
||||
const selectedDistrict = state.districts.find(item => item.value === district);
|
||||
if (selectedDistrict) {
|
||||
city = selectedDistrict.label; // 传递行政区名称,如"静安"
|
||||
county = selectedDistrict.label; // 传递行政区名称,如"静安"
|
||||
}
|
||||
}
|
||||
// 如果是"全城"(distanceFilter 为空),city 保持 undefined,不会被传递
|
||||
@@ -246,12 +245,13 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
distanceFilter: distanceFilter,
|
||||
// 显式设置 province,确保始终使用 state.area 中的最新值
|
||||
province: last_location_province, // 始终使用 state.area 中的 province,确保城市参数一致
|
||||
city: areaCity,
|
||||
};
|
||||
|
||||
// 只在有值时添加 city 参数
|
||||
if (city) {
|
||||
searchOption.city = city;
|
||||
}
|
||||
if (county) {
|
||||
searchOption.county = county;
|
||||
}
|
||||
|
||||
const params = {
|
||||
pageOption: currentPageState?.pageOption,
|
||||
@@ -374,7 +374,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
|
||||
try {
|
||||
const searchParams = getSearchParams() || {};
|
||||
|
||||
|
||||
// 并发请求:常规列表、智能排序列表
|
||||
const [listResSettled, integrateResSettled] = await Promise.allSettled([
|
||||
getGamesList({
|
||||
@@ -447,7 +447,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
const state = get();
|
||||
const { getSearchParams } = state;
|
||||
const searchParams = getSearchParams() || {};
|
||||
|
||||
|
||||
// 使用和 games/integrate_list 相同的参数构建逻辑
|
||||
const params = {
|
||||
...searchParams,
|
||||
@@ -457,7 +457,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
isRefresh: true, // 和 integrate_list 保持一致
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
console.log("fetchGetGamesCount 参数:", { area: state.area, params: JSON.stringify(params) });
|
||||
const resData = (await getGamesCount(params)) || {};
|
||||
const gamesNum = resData?.data?.count || 0;
|
||||
@@ -551,7 +551,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
const state = get();
|
||||
const { currentPageState } = state.getCurrentPageState();
|
||||
const filterOptions = { ...currentPageState?.filterOptions, ...payload };
|
||||
|
||||
|
||||
// 计算筛选数量:排除 dateRange、ntrp 默认值,以及空数组和空字符串
|
||||
const filterCount = Object.entries(filterOptions).filter(([key, value]) => {
|
||||
if (key === 'dateRange') return false; // 日期区间不算筛选
|
||||
@@ -572,7 +572,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
filterCount,
|
||||
pageOption: defaultPageOption,
|
||||
});
|
||||
|
||||
|
||||
// 使用 Promise.resolve 确保状态更新后再调用接口
|
||||
// 先调用列表接口,然后在列表接口完成后调用数量接口
|
||||
Promise.resolve().then(async () => {
|
||||
@@ -590,7 +590,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
const { currentPageState } = state.getCurrentPageState();
|
||||
const { distanceQuickFilter } = currentPageState || {};
|
||||
const newDistanceQuickFilter = { ...distanceQuickFilter, ...payload };
|
||||
|
||||
|
||||
// 先更新状态
|
||||
state.updateCurrentPageState({
|
||||
distanceQuickFilter: newDistanceQuickFilter,
|
||||
@@ -729,18 +729,18 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
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
|
||||
// 从 area 中获取省份,area 格式: [ 省份, 城市]
|
||||
const province = state.area?.at(0) || "上海";
|
||||
const cn_city = state.area?.at(1) || "上海市"; // area[1] 是省份
|
||||
|
||||
const res = await getDistricts({
|
||||
province,
|
||||
city: cn_city
|
||||
});
|
||||
|
||||
|
||||
if (res.code === 0 && res.data) {
|
||||
const districts = res.data.map((item) => ({
|
||||
label: item.cn_city,
|
||||
label: item.cn_county,
|
||||
value: item.id.toString(),
|
||||
id: item.id,
|
||||
}));
|
||||
|
||||
@@ -36,6 +36,7 @@ const getTimeNextDate = (time: string) => {
|
||||
// 请求锁,避免重复调用
|
||||
let isFetchingLastTestResult = false;
|
||||
let isCheckingNicknameStatus = false;
|
||||
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
||||
|
||||
export const useUser = create<UserState>()((set) => ({
|
||||
user: {},
|
||||
@@ -44,41 +45,50 @@ export const useUser = create<UserState>()((set) => ({
|
||||
const res = await fetchUserProfile();
|
||||
const userData = res.data;
|
||||
set({ user: userData });
|
||||
|
||||
|
||||
// 优先使用缓存中的城市,不使用用户信息中的位置
|
||||
// 检查是否有缓存的城市
|
||||
const CITY_CACHE_KEY = "USER_SELECTED_CITY";
|
||||
|
||||
const cachedCity = (Taro as any).getStorageSync?.(CITY_CACHE_KEY);
|
||||
|
||||
|
||||
|
||||
|
||||
if (cachedCity && Array.isArray(cachedCity) && cachedCity.length === 2) {
|
||||
// 如果有缓存的城市,使用缓存,不更新 area
|
||||
console.log("[userStore] 检测到缓存的城市,使用缓存,不更新 area");
|
||||
return userData;
|
||||
}
|
||||
|
||||
|
||||
// 只有当没有缓存时,才使用用户信息中的位置
|
||||
if (userData?.last_location_province) {
|
||||
const listStore = useListStore.getState();
|
||||
const currentArea = listStore.area;
|
||||
|
||||
// 只有当 area 不存在时才使用用户信息中的位置
|
||||
if (!currentArea) {
|
||||
const newArea: [string, string] = ["中国", userData.last_location_province];
|
||||
const newArea: [string, string] = [userData.last_location_province||"", userData.last_location_city||""];
|
||||
listStore.updateArea(newArea);
|
||||
// 保存到缓存
|
||||
try {
|
||||
(Taro as any).setStorageSync?.(CITY_CACHE_KEY, newArea);
|
||||
} catch (error) {
|
||||
console.error("保存城市缓存失败:", error);
|
||||
}
|
||||
useUser.getState().updateCache(newArea);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
// 更新缓存
|
||||
updateCache: async (newArea: [string, string]) => {
|
||||
try {
|
||||
(Taro as any).setStorageSync?.(CITY_CACHE_KEY, newArea);
|
||||
} catch (error) {
|
||||
console.error("保存城市缓存失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
updateUserInfo: async (userInfo: Partial<UserInfoType>) => {
|
||||
try {
|
||||
// 先更新后端
|
||||
@@ -86,18 +96,18 @@ export const useUser = create<UserState>()((set) => ({
|
||||
// 然后立即更新本地状态(乐观更新)
|
||||
set((state) => {
|
||||
const newUser = { ...state.user, ...userInfo };
|
||||
|
||||
|
||||
// 当 userLastLocationProvince 更新时,同步更新 area
|
||||
if (userInfo.last_location_province) {
|
||||
const listStore = useListStore.getState();
|
||||
const currentArea = listStore.area;
|
||||
// 只有当 area 不存在或与 userLastLocationProvince 不一致时才更新
|
||||
if (!currentArea || currentArea[1] !== userInfo.last_location_province) {
|
||||
const newArea: [string, string] = ["中国", userInfo.last_location_province];
|
||||
const newArea: [string, string] = [userInfo.last_location_province || "", userInfo.last_location_city || ""];
|
||||
listStore.updateArea(newArea);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { user: newUser };
|
||||
});
|
||||
// 不再每次都重新获取完整用户信息,减少请求次数
|
||||
@@ -195,6 +205,7 @@ export const useNicknameChangeStatus = () =>
|
||||
export const useUserActions = () =>
|
||||
useUser((state) => ({
|
||||
fetchUserInfo: state.fetchUserInfo,
|
||||
updateCache: state.updateCache,
|
||||
updateUserInfo: state.updateUserInfo,
|
||||
checkNicknameChangeStatus: state.checkNicknameChangeStatus,
|
||||
updateNickname: state.updateNickname,
|
||||
|
||||
Reference in New Issue
Block a user