Merge branch 'master' into feat/liujie
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -45,7 +45,7 @@ const ListCard: React.FC<ListCardProps> = ({
|
||||
className="image"
|
||||
mode="aspectFill"
|
||||
lazyLoad
|
||||
defaultSource="https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center"
|
||||
defaultSource={require("@/static/emptyStatus/publish-empty-card.png")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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: '仅上海地区开放,您可加入社群或切换城市',
|
||||
|
||||
@@ -89,25 +89,15 @@ const RadarChart: React.FC = forwardRef((props, ref) => {
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
// 标签
|
||||
const offset = 10;
|
||||
const textX = center.x + (radius + offset) * Math.cos(angle);
|
||||
const textY = center.y + (radius + offset) * Math.sin(angle);
|
||||
// 标签:沿轴线外侧延伸,文字中心对齐轴线端点
|
||||
const labelOffset = 28;
|
||||
const textX = center.x + (radius + labelOffset) * Math.cos(angle);
|
||||
const textY = center.y + (radius + labelOffset) * Math.sin(angle);
|
||||
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
if (
|
||||
Math.abs(angle) < 0.01 ||
|
||||
Math.abs(Math.abs(angle) - Math.PI) < 0.01
|
||||
) {
|
||||
ctx.textAlign = "center";
|
||||
} else if (angle > -Math.PI / 2 && angle < Math.PI / 2) {
|
||||
ctx.textAlign = "left";
|
||||
} else {
|
||||
ctx.textAlign = "right";
|
||||
}
|
||||
ctx.textAlign = "center";
|
||||
|
||||
ctx.fillText(label, textX, textY);
|
||||
});
|
||||
|
||||
@@ -102,26 +102,15 @@ const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref)
|
||||
ctx.lineWidth = 1 * (radarSize / 200); // 根据2倍图调整线宽
|
||||
ctx.stroke();
|
||||
|
||||
// 标签 - 文字显示在圆圈外面
|
||||
const offset = 10 * (radarSize / 200); // 文字距离圆圈的偏移量(2倍图)
|
||||
const textX = center.x + (radius + offset) * Math.cos(angle);
|
||||
const textY = center.y + (radius + offset) * Math.sin(angle);
|
||||
// 标签:沿轴线外侧延伸,文字中心对齐轴线端点(与 index.tsx 一致)
|
||||
const labelOffset = 28 * (radarSize / 200); // 文字距离圆圈的偏移量(2倍图)
|
||||
const textX = center.x + (radius + labelOffset) * Math.cos(angle);
|
||||
const textY = center.y + (radius + labelOffset) * Math.sin(angle);
|
||||
|
||||
ctx.font = `${12 * (radarSize / 200)}px sans-serif`; // 根据2倍图调整字体大小
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
// 调整文字对齐方式
|
||||
if (
|
||||
Math.abs(angle) < 0.01 ||
|
||||
Math.abs(Math.abs(angle) - Math.PI) < 0.01
|
||||
) {
|
||||
ctx.textAlign = "center";
|
||||
} else if (angle > -Math.PI / 2 && angle < Math.PI / 2) {
|
||||
ctx.textAlign = "left";
|
||||
} else {
|
||||
ctx.textAlign = "right";
|
||||
}
|
||||
ctx.textAlign = "center";
|
||||
|
||||
ctx.fillText(label, textX, textY);
|
||||
});
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { EvaluateScene } from "@/store/evaluateStore";
|
||||
import { waitForAuthInit } from "@/utils/authInit";
|
||||
import "./index.scss";
|
||||
import { useRef, useEffect, useState, useMemo } from "react";
|
||||
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||
|
||||
const ListContainer = (props) => {
|
||||
const {
|
||||
@@ -28,6 +29,7 @@ const ListContainer = (props) => {
|
||||
collapse = false,
|
||||
defaultShowNum,
|
||||
evaluateFlag,
|
||||
enableHomeCards = false, // 仅首页需要 banner 和 NTRP 测评卡片
|
||||
listLoadErrorWrapperHeight,
|
||||
listLoadErrorWidth,
|
||||
listLoadErrorHeight,
|
||||
@@ -44,7 +46,7 @@ const ListContainer = (props) => {
|
||||
const { fetchUserInfo, fetchLastTestResult } = useUserActions();
|
||||
// 使用全局状态中的测试结果,避免重复调用接口
|
||||
const lastTestResult = useLastTestResult();
|
||||
|
||||
const { bannerListImage, bannerDetailImage, bannerListIndex = 0 } = useDictionaryStore((s) => s.bannerDict) || {};
|
||||
useReachBottom(() => {
|
||||
// 加载更多方法
|
||||
if (loading) {
|
||||
@@ -93,10 +95,10 @@ const ListContainer = (props) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 获取测试结果,判断最近一个月是否有测试记录
|
||||
// 获取测试结果,判断最近一个月是否有测试记录(仅首页需要)
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
if (!evaluateFlag) return;
|
||||
if (!evaluateFlag || !enableHomeCards) return;
|
||||
// 先等待静默登录完成
|
||||
await waitForAuthInit();
|
||||
// 然后再获取用户信息
|
||||
@@ -111,7 +113,7 @@ const ListContainer = (props) => {
|
||||
}
|
||||
};
|
||||
init();
|
||||
}, [evaluateFlag, userInfo, lastTestResult, fetchLastTestResult]);
|
||||
}, [evaluateFlag, enableHomeCards, userInfo, lastTestResult, fetchLastTestResult]);
|
||||
|
||||
// 从全局状态中获取测试状态
|
||||
const hasTestInLastMonth = lastTestResult?.has_test_in_last_month || false;
|
||||
@@ -130,60 +132,74 @@ const ListContainer = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
||||
function insertEvaluateCard(list) {
|
||||
if (!evaluateFlag)
|
||||
return showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||
if (!list || list.length === 0) {
|
||||
return list;
|
||||
}
|
||||
// 如果最近一个月有测试记录,则不插入 card
|
||||
if (hasTestInLastMonth) {
|
||||
return showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||
}
|
||||
|
||||
if (list.length <= 2) {
|
||||
return [...list, { type: "evaluateCard" }];
|
||||
}
|
||||
const [item1, item2, ...rest] = list;
|
||||
// 插入 banner 卡片
|
||||
function insertBannerCard(list) {
|
||||
if (!bannerListImage) return list;
|
||||
if (!list || !Array.isArray(list)) return list ?? [];
|
||||
return [
|
||||
item1,
|
||||
item2,
|
||||
{ type: "evaluateCard" },
|
||||
...(showNumber !== undefined ? rest.slice(0, showNumber - 3) : rest),
|
||||
...list.slice(0, Number(bannerListIndex)),
|
||||
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
||||
...list.slice(Number(bannerListIndex))
|
||||
];
|
||||
}
|
||||
|
||||
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
||||
// insertBannerCard 需在最后统一执行,否则前面分支直接 return 时 banner 不会被插入
|
||||
function insertEvaluateCard(list) {
|
||||
let result: any[];
|
||||
|
||||
if (!evaluateFlag) {
|
||||
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||
} else if (!list || list.length === 0) {
|
||||
result = list;
|
||||
} else if (hasTestInLastMonth) {
|
||||
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
||||
} else if (list.length <= 2) {
|
||||
result = [...list, { type: "evaluateCard" }];
|
||||
} else {
|
||||
const [item1, item2, ...rest] = list;
|
||||
result = [
|
||||
item1,
|
||||
item2,
|
||||
{ type: "evaluateCard" },
|
||||
...(showNumber !== undefined ? rest.slice(0, showNumber - 3) : rest),
|
||||
];
|
||||
}
|
||||
|
||||
return insertBannerCard(result);
|
||||
}
|
||||
|
||||
const memoizedList = useMemo(
|
||||
() => insertEvaluateCard(data),
|
||||
[evaluateFlag, data, hasTestInLastMonth, showNumber]
|
||||
() => (enableHomeCards ? insertEvaluateCard(data) : data),
|
||||
[enableHomeCards, evaluateFlag, data, hasTestInLastMonth, showNumber, bannerListImage, bannerDetailImage, bannerListIndex]
|
||||
);
|
||||
|
||||
// 渲染 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: "100px",
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -211,12 +227,12 @@ const ListContainer = (props) => {
|
||||
return (
|
||||
<>
|
||||
{memoizedList.map((match, index) => {
|
||||
if (match.type === "banner") {
|
||||
if (enableHomeCards && match?.type === "banner") {
|
||||
return renderBanner(match, index);
|
||||
}
|
||||
if (match.type === "evaluateCard") {
|
||||
if (enableHomeCards && match?.type === "evaluateCard") {
|
||||
return (
|
||||
<NTRPTestEntryCard key="evaluate" type={EvaluateScene.list} />
|
||||
<NTRPTestEntryCard key={`evaluate-${index}`} type={EvaluateScene.list} />
|
||||
);
|
||||
}
|
||||
return <ListCard key={match?.id || index} {...match} />;
|
||||
|
||||
@@ -128,7 +128,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
const endTime = dayjs(end_time);
|
||||
const dayofWeek = DayOfWeekMap.get(startTime.day());
|
||||
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
|
||||
Taro.showLoading({ title: "生成中..." });
|
||||
// Taro.showLoading({ title: "生成中..." });
|
||||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||||
page: "game_pages/detail/index",
|
||||
scene: `id=${id}`,
|
||||
@@ -137,6 +137,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
qrCodeUrlRes.data.qr_code_base64
|
||||
);
|
||||
await delay(100);
|
||||
// Taro.showLoading({ title: "生成中..." });
|
||||
const url = await generatePosterImage({
|
||||
playType: play_type,
|
||||
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
||||
@@ -152,7 +153,7 @@ export default forwardRef(({ id, from, detail, userInfo }, ref) => {
|
||||
time: `${startTime.format("ah")}点 ${gameLength}`,
|
||||
qrCodeUrl,
|
||||
});
|
||||
Taro.hideLoading();
|
||||
// Taro.hideLoading();
|
||||
Taro.showShareImageMenu({
|
||||
path: url,
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
@@ -602,6 +627,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
reload={refreshMatches}
|
||||
loadMoreMatches={loadMoreMatches}
|
||||
evaluateFlag
|
||||
enableHomeCards
|
||||
/>
|
||||
</ScrollView>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,12 +67,6 @@ const MainPage: React.FC = () => {
|
||||
try {
|
||||
await fetchUserInfo();
|
||||
await checkNicknameChangeStatus();
|
||||
// 启动时预取 Banner 字典(与业务无强依赖,失败不影响主流程)
|
||||
try {
|
||||
await useDictionaryStore.getState().fetchBannerDictionary();
|
||||
} catch (e) {
|
||||
console.error("预取 Banner 字典失败:", e);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
}
|
||||
|
||||
@@ -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,9 +10,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: calc(100vh - 98px);
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 示例消息卡片区域
|
||||
@@ -163,7 +162,6 @@
|
||||
|
||||
&__qr_image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__qr_placeholder {
|
||||
|
||||
@@ -21,10 +21,10 @@ const OrderCheck = () => {
|
||||
|
||||
//TODO: get order msg from id
|
||||
const handlePay = async () => {
|
||||
Taro.showLoading({
|
||||
title: '支付中...',
|
||||
mask: true
|
||||
})
|
||||
// Taro.showLoading({
|
||||
// title: '支付中...',
|
||||
// mask: true
|
||||
// })
|
||||
const res = await orderService.createOrder(Number(gameId))
|
||||
if (res.code === 0) {
|
||||
const { payment_required, payment_params } = res.data
|
||||
@@ -37,7 +37,7 @@ const OrderCheck = () => {
|
||||
signType,
|
||||
paySign,
|
||||
success: async () => {
|
||||
Taro.hideLoading()
|
||||
// Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
@@ -48,7 +48,7 @@ const OrderCheck = () => {
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
Taro.hideLoading()
|
||||
// Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
|
||||
@@ -193,7 +193,7 @@ const NewFollow = () => {
|
||||
<View className="follow-left" onClick={() => handleUserClick(item.user_id)}>
|
||||
<Image
|
||||
className="user-avatar" mode="aspectFill"
|
||||
src={item.user_avatar || "https://img.yzcdn.cn/vant/cat.jpeg"}
|
||||
src={item.user_avatar || require("@/static/userInfo/default_avatar.svg")}
|
||||
|
||||
/>
|
||||
|
||||
|
||||
@@ -216,9 +216,8 @@ function Intro() {
|
||||
});
|
||||
}
|
||||
Taro.redirectTo({
|
||||
url: `/other_pages/ntrp-evaluate/index?stage=${type}${
|
||||
type === StageType.RESULT ? `&id=${id}` : ""
|
||||
}`,
|
||||
url: `/other_pages/ntrp-evaluate/index?stage=${type}${type === StageType.RESULT ? `&id=${id}` : ""
|
||||
}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -539,18 +538,21 @@ function Result() {
|
||||
const res = await evaluateService.getTestResult({ record_id: Number(id) });
|
||||
if (res.code === 0) {
|
||||
setResult(res.data);
|
||||
// delay(1000);
|
||||
setRadarData(
|
||||
adjustRadarLabels(
|
||||
Object.entries(res.data.radar_data.abilities).map(([key, value]) => [
|
||||
key,
|
||||
Math.min(
|
||||
100,
|
||||
Math.floor((value.current_score / value.max_score) * 100)
|
||||
),
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
const sortOrder = res.data.sort || [];
|
||||
const abilities = res.data.radar_data.abilities;
|
||||
const sortedKeys = sortOrder.filter((k) => k in abilities);
|
||||
const remainingKeys = Object.keys(abilities).filter((k) => !sortOrder.includes(k));
|
||||
const allKeys = [...sortedKeys, ...remainingKeys];
|
||||
let radarData: [string, number][] = allKeys.map((key) => [
|
||||
key,
|
||||
Math.min(
|
||||
100,
|
||||
Math.floor((abilities[key].current_score / abilities[key].max_score) * 100)
|
||||
),
|
||||
]);
|
||||
// 直接使用接口 sort 顺序,不经过 adjustRadarLabels 重新排序
|
||||
setRadarData(radarData);
|
||||
updateUserLevel(res.data.record_id, res.data.ntrp_level);
|
||||
}
|
||||
}
|
||||
@@ -698,13 +700,12 @@ function Result() {
|
||||
}
|
||||
const currentPage = getCurrentFullPath();
|
||||
Taro.redirectTo({
|
||||
url: `/login_pages/index/index${
|
||||
currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : ""
|
||||
}`,
|
||||
url: `/login_pages/index/index${currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : ""
|
||||
}`,
|
||||
});
|
||||
}
|
||||
|
||||
function handleGo() {}
|
||||
function handleGo() { }
|
||||
|
||||
return (
|
||||
<View className={styles.resultContainer}>
|
||||
|
||||
@@ -51,7 +51,6 @@ class CommonApiService {
|
||||
data: results.map(result => result.data)
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
} finally {
|
||||
Taro.hideLoading()
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ class GameDetailService {
|
||||
width: number
|
||||
}>> {
|
||||
return httpService.post('/user/generate_qrcode', req, {
|
||||
showLoading: false
|
||||
showLoading: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface TestResultData {
|
||||
level_img?: string; // 等级图片URL
|
||||
radar_data: RadarData;
|
||||
answers: Answer[];
|
||||
sort?: string[]; // 雷达图能力项排序,如 ["正手球质", "正手控制", ...]
|
||||
}
|
||||
|
||||
// 单条测试记录
|
||||
|
||||
@@ -129,23 +129,30 @@ class HttpService {
|
||||
|
||||
// 隐藏loading(支持多个并发请求)
|
||||
private hideLoading(): void {
|
||||
this.loadingCount = Math.max(0, this.loadingCount - 1)
|
||||
try {
|
||||
this.loadingCount = Math.max(0, this.loadingCount - 1)
|
||||
|
||||
// 只有所有请求都完成时才隐藏loading
|
||||
if (this.loadingCount === 0) {
|
||||
// 清除之前的延时器
|
||||
if (this.hideLoadingTimer) {
|
||||
clearTimeout(this.hideLoadingTimer)
|
||||
this.hideLoadingTimer = null
|
||||
// 只有所有请求都完成时才隐藏loading
|
||||
if (this.loadingCount === 0) {
|
||||
// 清除之前的延时器
|
||||
if (this.hideLoadingTimer) {
|
||||
clearTimeout(this.hideLoadingTimer)
|
||||
this.hideLoadingTimer = null
|
||||
}
|
||||
|
||||
// 延时300ms后隐藏loading,避免频繁切换
|
||||
this.hideLoadingTimer = setTimeout(() => {
|
||||
Taro.hideLoading()
|
||||
this.currentLoadingText = ''
|
||||
this.hideLoadingTimer = null
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// 延时300ms后隐藏loading,避免频繁切换
|
||||
this.hideLoadingTimer = setTimeout(() => {
|
||||
Taro.hideLoading()
|
||||
this.currentLoadingText = ''
|
||||
this.hideLoadingTimer = null
|
||||
}, 800)
|
||||
}
|
||||
catch (e) {
|
||||
console.warn(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
@@ -175,7 +182,7 @@ class HttpService {
|
||||
url: '/login_pages/index/index'
|
||||
})
|
||||
reject(new Error('用户不存在'))
|
||||
return response.data
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +194,7 @@ class HttpService {
|
||||
} else {
|
||||
reject(response.data)
|
||||
}
|
||||
return response.data
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |
@@ -20,7 +20,6 @@ interface DictionaryState {
|
||||
bannerDetailImage: string
|
||||
bannerListIndex: string
|
||||
} | null
|
||||
fetchBannerDictionary: () => Promise<void>
|
||||
}
|
||||
|
||||
// 创建字典Store
|
||||
@@ -36,20 +35,32 @@ 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,bannerListImage,bannerDetailImage,bannerListIndex';
|
||||
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({
|
||||
dictionaryData: dictionaryData || {},
|
||||
isLoading: false
|
||||
})
|
||||
|
||||
set({
|
||||
bannerDict: {
|
||||
bannerListImage: response.data.bannerListImage || '',
|
||||
bannerDetailImage: response.data.bannerDetailImage || '',
|
||||
bannerListIndex: (response.data.bannerListIndex ?? '').toString(),
|
||||
}
|
||||
})
|
||||
|
||||
console.log('字典数据获取成功:', response.data)
|
||||
} else {
|
||||
throw new Error(response.message || '获取字典数据失败')
|
||||
@@ -64,26 +75,7 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
// 获取 Banner 字典(启动时或手动调用)
|
||||
fetchBannerDictionary: async () => {
|
||||
try {
|
||||
const keys = 'bannerListImage,bannerDetailImage,bannerListIndex';
|
||||
const response = await commonApi.getDictionaryManyKey(keys)
|
||||
if (response.code === 0 && response.data) {
|
||||
const data = response.data || {};
|
||||
set({
|
||||
bannerDict: {
|
||||
bannerListImage: data.bannerListImage || '',
|
||||
bannerDetailImage: data.bannerDetailImage || '',
|
||||
bannerListIndex: (data.bannerListIndex ?? '').toString(),
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// 保持静默,避免影响启动流程
|
||||
console.error('获取 Banner 字典失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// 获取字典值
|
||||
getDictionaryValue: (key: string, defaultValue?: any) => {
|
||||
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
getCityQrCode,
|
||||
getDistricts,
|
||||
} from "../services/listApi";
|
||||
// 不再在这里请求 banner 字典,统一由 dictionaryStore 启动时获取
|
||||
import { useDictionaryStore } from "./dictionaryStore";
|
||||
import {
|
||||
ListActions,
|
||||
IFilterOptions,
|
||||
@@ -20,40 +18,20 @@ import {
|
||||
IPayload,
|
||||
} from "../../types/list/types";
|
||||
|
||||
// 将 banner 按索引插入到列表的工具方法(0基;长度不足则插末尾;先移除已存在的 banner)
|
||||
function insertBannersToRows(rows: any[], dictData: any) {
|
||||
if (!Array.isArray(rows) || !dictData) return rows;
|
||||
const img = (dictData?.bannerListImage || "").trim();
|
||||
const indexRaw = (dictData?.bannerListIndex || "").toString().trim();
|
||||
if (!img) return rows;
|
||||
const parsed = parseInt(indexRaw, 10);
|
||||
const normalized = Number.isFinite(parsed) ? parsed : 0;
|
||||
// 先移除已有的 banner,确保列表中仅一条 banner
|
||||
const resultRows = rows?.filter((item) => item?.type !== "banner") || [];
|
||||
const target = Math.max(0, Math.min(normalized, resultRows.length));
|
||||
resultRows.splice(target, 0, {
|
||||
type: "banner",
|
||||
id: `banner-${target}`,
|
||||
banner_image_url: img,
|
||||
banner_detail_url: (dictData?.bannerDetailImage || "").trim(),
|
||||
} as any);
|
||||
return resultRows;
|
||||
}
|
||||
|
||||
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 +192,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 +223,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,
|
||||
@@ -272,14 +250,11 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState;
|
||||
const currentData = currentPageState?.data || [];
|
||||
const newData = isAppend ? [...currentData, ...(data || [])] : (data || []);
|
||||
// 从字典缓存获取 banner,并将其插入到最终列表指定位置(全局索引)
|
||||
const dictData = useDictionaryStore.getState().bannerDict;
|
||||
const processedData = dictData ? insertBannersToRows(newData, dictData) : newData;
|
||||
state.updateCurrentPageState({
|
||||
data: processedData,
|
||||
data: newData,
|
||||
isHasMoreData,
|
||||
// 使用插入后的最终数据判断是否显示空状态,避免有 banner 时仍显示空
|
||||
isShowNoData: processedData?.length === 0,
|
||||
isShowNoData: newData?.length === 0,
|
||||
});
|
||||
|
||||
set({
|
||||
@@ -374,7 +349,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
|
||||
try {
|
||||
const searchParams = getSearchParams() || {};
|
||||
|
||||
|
||||
// 并发请求:常规列表、智能排序列表
|
||||
const [listResSettled, integrateResSettled] = await Promise.allSettled([
|
||||
getGamesList({
|
||||
@@ -447,7 +422,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 +432,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 +526,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 +547,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
filterCount,
|
||||
pageOption: defaultPageOption,
|
||||
});
|
||||
|
||||
|
||||
// 使用 Promise.resolve 确保状态更新后再调用接口
|
||||
// 先调用列表接口,然后在列表接口完成后调用数量接口
|
||||
Promise.resolve().then(async () => {
|
||||
@@ -590,7 +565,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 +704,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,
|
||||
|
||||
@@ -534,23 +534,23 @@ const drawShareCard = async (ctx: any, data: ShareCardData, offscreen: any): Pro
|
||||
ctx.drawImage(locationPath, iconX, locationInfoY, iconSize, iconSize)
|
||||
drawBoldText(ctx, data.venueName, danDaX, locationInfoY + 10, locationFontSize, '#000000')
|
||||
|
||||
try {
|
||||
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
||||
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
||||
wxAny.canvasToTempFilePath({
|
||||
canvas: offscreen,
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
success: (res: any) => {
|
||||
console.log('===res666', res)
|
||||
resolve(res.tempFilePath)
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
return
|
||||
}
|
||||
} catch { }
|
||||
reject(new Error('无法导出图片(OffscreenCanvas 转文件失败)'))
|
||||
try {
|
||||
const wxAny: any = (typeof (globalThis as any) !== 'undefined' && (globalThis as any).wx) ? (globalThis as any).wx : null
|
||||
if (wxAny && typeof wxAny.canvasToTempFilePath === 'function') {
|
||||
wxAny.canvasToTempFilePath({
|
||||
canvas: offscreen,
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
success: (res: any) => {
|
||||
console.log('===res666', res)
|
||||
resolve(res.tempFilePath)
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
return
|
||||
}
|
||||
} catch { }
|
||||
reject(new Error('无法导出图片(OffscreenCanvas 转文件失败)'))
|
||||
console.log('Canvas绘制命令已发送')
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user