diff --git a/src/components/Picker/PopupPicker.tsx b/src/components/Picker/PopupPicker.tsx index e4197af..ac7cdc0 100644 --- a/src/components/Picker/PopupPicker.tsx +++ b/src/components/Picker/PopupPicker.tsx @@ -51,7 +51,12 @@ const PopupPicker = ({ }: PickerProps) => { const [defaultValue, setDefaultValue] = useState<(string | number)[]>([]); const [defaultOptions, setDefaultOptions] = useState([]); + const [pickerCurrentValue, setPickerCurrentValue] = useState<(string | number)[]>(value); + const changePicker = (options: any[], values: any, columnIndex: number) => { + // 更新 Picker 的当前值 + setPickerCurrentValue(values); + if (onChange) { console.log("picker onChange", columnIndex, values, options); if ( @@ -77,8 +82,19 @@ const PopupPicker = ({ } }; + // 处理 Picker 的确认事件,获取当前选中的值 + const handlePickerConfirm = (options: PickerOption[], values: (string | number)[]) => { + setPickerCurrentValue(values); + setDefaultValue(values); + }; + const handleConfirm = () => { - onChange(defaultValue); + // 如果用户滚动过(defaultValue 不为空),使用 defaultValue + // 如果用户没有滚动(defaultValue 为空),使用传入的默认值(即当前显示的默认选项) + const valueToSave = defaultValue.length > 0 ? defaultValue : value; + if (onChange && valueToSave.length > 0) { + onChange(valueToSave); + } setvisible(false); }; @@ -97,11 +113,19 @@ const PopupPicker = ({ } }, [type]); - // useEffect(() => { - // if (value.length > 0 && defaultOptions.length > 0) { - // setDefaultValue([...value]) - // } - // }, [value, defaultOptions]) + // 当选择器打开时,初始化 defaultValue 和 pickerCurrentValue + useEffect(() => { + if (visible) { + if (value.length > 0) { + setDefaultValue([...value]); + setPickerCurrentValue([...value]); + } else { + // 如果 value 为空,重置为初始状态 + setDefaultValue([]); + setPickerCurrentValue([]); + } + } + }, [visible, value]); return ( <> diff --git a/src/other_pages/ntrp-evaluate/index.tsx b/src/other_pages/ntrp-evaluate/index.tsx index e9119b3..8469bf1 100644 --- a/src/other_pages/ntrp-evaluate/index.tsx +++ b/src/other_pages/ntrp-evaluate/index.tsx @@ -147,9 +147,17 @@ function Intro() { const [ready, setReady] = useState(false); const { setCallback } = useEvaluate(); - const { last_test_result: { ntrp_level, create_time, id } = {} } = - ntrpData || {}; - const lastTestTime = dayjs(create_time).format("YYYY年M月D日"); + const { last_test_result = null } = ntrpData || {}; + const { ntrp_level, create_time, id } = last_test_result || {}; + const lastTestTime = create_time ? dayjs(create_time).format("YYYY年M月D日") : ""; + + // 组件初始化时立即获取用户信息 + useEffect(() => { + // 如果用户信息为空,立即获取 + if (!userInfo || Object.keys(userInfo).length === 0) { + fetchUserInfo(); + } + }, []); useEffect(() => { getLastResult(); @@ -160,7 +168,10 @@ function Intro() { if (res.code === 0) { setNtrpData(res.data); if (res.data.has_ntrp_level) { - fetchUserInfo(); + // 确保用户信息已加载 + if (!userInfo || Object.keys(userInfo).length === 0) { + await fetchUserInfo(); + } } setReady(true); } @@ -197,7 +208,7 @@ function Intro() { @@ -225,7 +236,7 @@ function Intro() { NTRP - {formatNtrpDisplay(ntrp_level)} + {formatNtrpDisplay(ntrp_level || "")} 变线+网前,下一步就是赢比赛! @@ -439,9 +450,20 @@ function Result() { [propName: string, prop: number][] >([]); + // 组件初始化时立即获取用户信息 + useEffect(() => { + // 如果用户信息为空,立即获取 + if (!userInfo || Object.keys(userInfo).length === 0) { + fetchUserInfo(); + } + }, []); + useEffect(() => { getResultById(); - fetchUserInfo(); + // 确保用户信息已加载 + if (!userInfo || Object.keys(userInfo).length === 0) { + fetchUserInfo(); + } }, [id]); async function getResultById() { @@ -548,7 +570,7 @@ function Result() { async function handleSaveImage() { console.log(userInfo); - if (!userInfo.id) { + if (!userInfo?.id) { return; } Taro.getSetting().then(async (res) => { @@ -597,7 +619,7 @@ function Result() { }); function handleAuth() { - if (userInfo.id) { + if (userInfo?.id) { return true; } const currentPage = getCurrentFullPath(); @@ -616,7 +638,7 @@ function Result() { @@ -649,9 +671,9 @@ function Result() { 重新测试 - {userInfo.id ? ( + {userInfo?.id ? ( - 你的 NTRP 水平已更新为 {formatNtrpDisplay(result?.ntrp_level)} + 你的 NTRP 水平已更新为 {formatNtrpDisplay(result?.ntrp_level || "")} (可在个人信息中修改) ) : ( @@ -669,7 +691,7 @@ function Result() { diff --git a/src/store/userStore.ts b/src/store/userStore.ts index 005af59..5c22a0e 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -11,27 +11,48 @@ export interface UserState { updateUserInfo: (userInfo: Partial) => void; } -const fetchUserInfo = async (set) => { - try { - const res = await fetchUserProfile(); - set({ user: res.data }); - return res.data; - } catch {} +// 请求锁,防止重复请求 +let fetchingUserInfo = false; +let fetchUserInfoPromise: Promise | null = null; + +const fetchUserInfoWithLock = async (set) => { + // 如果正在请求,直接返回现有的 Promise + if (fetchingUserInfo && fetchUserInfoPromise) { + return fetchUserInfoPromise; + } + + fetchingUserInfo = true; + fetchUserInfoPromise = (async () => { + try { + const res = await fetchUserProfile(); + set({ user: res.data }); + return res.data; + } catch (error) { + console.error("获取用户信息失败:", error); + return undefined; + } finally { + fetchingUserInfo = false; + fetchUserInfoPromise = null; + } + })(); + + return fetchUserInfoPromise; }; export const useUser = create()((set) => ({ user: {}, - fetchUserInfo: fetchUserInfo.bind(null, set), + fetchUserInfo: () => fetchUserInfoWithLock(set), updateUserInfo: async (userInfo: Partial) => { try { // 先更新后端 await updateUserProfile(userInfo); - // 然后立即更新本地状态 + // 然后立即更新本地状态(乐观更新) set((state) => ({ user: { ...state.user, ...userInfo }, })); - // 最后重新获取完整用户信息确保数据一致性 - await fetchUserInfo(set); + // 不再每次都重新获取完整用户信息,减少请求次数 + // 只有在更新头像等需要服务器返回新URL的字段时才需要重新获取 + // 如果需要确保数据一致性,可以在特定场景下手动调用 fetchUserInfo } catch (error) { console.error("更新用户信息失败:", error); throw error; diff --git a/src/user_pages/edit/index.tsx b/src/user_pages/edit/index.tsx index 530d30b..53da465 100644 --- a/src/user_pages/edit/index.tsx +++ b/src/user_pages/edit/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { View, Text, Image, ScrollView, Button } from "@tarojs/components"; +import { View, Text, Image, Button } from "@tarojs/components"; import { PopupPicker } from "@/components/Picker/index"; import Taro from "@tarojs/taro"; import "./index.scss"; @@ -19,18 +19,22 @@ const EditProfilePage: React.FC = () => { const user_info = useUserInfo(); // 表单状态,基于store中的用户信息初始化 - const [form_data, setFormData] = useState({ - nickname: (user_info as UserInfoType)?.nickname || "", - personal_profile: (user_info as UserInfoType)?.personal_profile || "", - occupation: (user_info as UserInfoType)?.occupation || "", - ntrp_level: (user_info as UserInfoType)?.ntrp_level || "4.0", - phone: (user_info as UserInfoType)?.phone || "", - gender: (user_info as UserInfoType)?.gender || "", - birthday: (user_info as UserInfoType)?.birthday || "2000-01-01", - country: (user_info as UserInfoType)?.country || "", - province: (user_info as UserInfoType)?.province || "", - city: (user_info as UserInfoType)?.city || "", - }); + const getInitialFormData = () => { + const info = user_info as UserInfoType; + return { + nickname: info?.nickname ?? "", + personal_profile: info?.personal_profile ?? "", + occupation: info?.occupation ?? "", + ntrp_level: info?.ntrp_level ?? "4.0", + phone: info?.phone ?? "", + gender: info?.gender ?? "", + birthday: info?.birthday ?? "2000-01-01", + country: info?.country ?? "", + province: info?.province ?? "", + city: info?.city ?? "", + }; + }; + const [form_data, setFormData] = useState(getInitialFormData()); // 加载状态 const [loading, setLoading] = useState(false); @@ -55,17 +59,18 @@ const EditProfilePage: React.FC = () => { // 监听store中的用户信息变化,同步到表单状态 useEffect(() => { if (user_info && Object.keys(user_info).length > 0) { + const info = user_info as UserInfoType; setFormData({ - nickname: (user_info as UserInfoType)?.nickname || "", - personal_profile: (user_info as UserInfoType)?.personal_profile || "", - occupation: (user_info as UserInfoType)?.occupation || "", - ntrp_level: (user_info as UserInfoType)?.ntrp_level || "4.0", - phone: (user_info as UserInfoType)?.phone || "", - gender: (user_info as UserInfoType)?.gender || "", - birthday: (user_info as UserInfoType)?.birthday || "2000-01-01", - country: (user_info as UserInfoType)?.country || "", - province: (user_info as UserInfoType)?.province || "", - city: (user_info as UserInfoType)?.city || "", + nickname: info?.nickname ?? "", + personal_profile: info?.personal_profile ?? "", + occupation: info?.occupation ?? "", + ntrp_level: info?.ntrp_level ?? "4.0", + phone: info?.phone ?? "", + gender: info?.gender ?? "", + birthday: info?.birthday ?? "2000-01-01", + country: info?.country ?? "", + province: info?.province ?? "", + city: info?.city ?? "", }); } }, [user_info]); @@ -183,6 +188,14 @@ const EditProfilePage: React.FC = () => { const handle_edit_modal_save = async (value: string) => { try { + // 验证值不能是 undefined 或 null + if (value === undefined || value === null) { + Taro.showToast({ + title: "数据不完整,请重新输入", + icon: "none", + }); + return; + } // 调用更新用户信息接口,只传递修改的字段 const update_data = { [editing_field]: value }; await updateUserInfo(update_data); @@ -224,11 +237,30 @@ const EditProfilePage: React.FC = () => { field !== null && !Array.isArray(field) ) { + // 验证对象中的值不能是 undefined + const hasUndefined = Object.values(field).some( + (v) => v === undefined || v === null + ); + if (hasUndefined) { + Taro.showToast({ + title: "数据不完整,请重新选择", + icon: "none", + }); + return; + } await updateUserInfo({ ...field }); // 更新表单状态(store会自动更新) setFormData((prev) => ({ ...prev, ...field })); } else { + // 验证值不能是 undefined + if (value === undefined || value === null) { + Taro.showToast({ + title: "数据不完整,请重新选择", + icon: "none", + }); + return; + } // 调用更新用户信息接口,只传递修改的字段 const update_data = { [field as string]: value }; await updateUserInfo(update_data); @@ -252,12 +284,26 @@ const EditProfilePage: React.FC = () => { // 处理性别选择 const handle_gender_change = (e: any) => { + if (!Array.isArray(e) || e.length === 0 || e[0] === undefined) { + Taro.showToast({ + title: "请选择性别", + icon: "none", + }); + return; + } const gender_value = e[0]; - handle_field_edit("gender", gender_value); + handle_field_edit("gender", String(gender_value)); }; // 处理生日选择 const handle_birthday_change = (e: any) => { + if (!Array.isArray(e) || e.length < 3 || e.some((v) => v === undefined)) { + Taro.showToast({ + title: "请完整选择生日", + icon: "none", + }); + return; + } const [year, month, day] = e; handle_field_edit( "birthday", @@ -270,20 +316,46 @@ const EditProfilePage: React.FC = () => { // 处理地区选择 const handle_location_change = (e: any) => { + if (!Array.isArray(e) || e.length < 3 || e.some((v) => v === undefined || v === null)) { + Taro.showToast({ + title: "请完整选择地区", + icon: "none", + }); + return; + } const [country, province, city] = e; - handle_field_edit({ country, province, city }); + handle_field_edit({ + country: String(country ?? ""), + province: String(province ?? ""), + city: String(city ?? "") + }); }; // 处理NTRP水平选择 const handle_ntrp_level_change = (e: any) => { + if (!Array.isArray(e) || e.length === 0 || e[0] === undefined) { + Taro.showToast({ + title: "请选择NTRP水平", + icon: "none", + }); + return; + } const ntrp_level_value = e[0]; - handle_field_edit("ntrp_level", ntrp_level_value); + handle_field_edit("ntrp_level", String(ntrp_level_value)); }; // 处理职业选择 const handle_occupation_change = (e: any) => { - const [country, province, city] = e; - handle_field_edit("occupation", `${country} ${province} ${city}`); + if (!Array.isArray(e) || e.length === 0 || e.some((v) => v === undefined || v === null)) { + Taro.showToast({ + title: "请完整选择职业", + icon: "none", + }); + return; + } + // 职业可能是多级联动,将所有选中的值用空格连接 + const occupation_value = e.map((v) => String(v ?? "")).filter(Boolean).join(" "); + handle_field_edit("occupation", occupation_value); }; // 处理退出登录 @@ -375,7 +447,7 @@ const EditProfilePage: React.FC = () => { }} /> {/* 主要内容 */} - + {loading ? ( 加载中... @@ -635,7 +707,7 @@ const EditProfilePage: React.FC = () => { )} - + {/* 编辑弹窗 */}