diff --git a/src/components/EditModal/index.tsx b/src/components/EditModal/index.tsx index abe1087..47807b4 100644 --- a/src/components/EditModal/index.tsx +++ b/src/components/EditModal/index.tsx @@ -33,7 +33,6 @@ const EditModal: React.FC = ({ const [isValid, setIsValid] = useState(true); const [isIllegal, setIsIllegal] = useState(false); const [hasIllegal, setHasIllegal] = useState(false); - const [canEdit, setCanEdit] = useState(true); // 使用全局键盘状态 const { keyboardHeight, @@ -89,15 +88,11 @@ const EditModal: React.FC = ({ value.length >= 2 && value.length <= maxLength && !hasIllegal && - !isIllegal && - canEdit; + !isIllegal; setIsValid(valid); - }, [ - value, hasIllegal, isIllegal, canEdit - ]) + }, [value, hasIllegal, isIllegal]); const handle_save = () => { - console.log("savexxxxxxx", isIllegal, hasIllegal, !isValid) if (isIllegal) { Taro.showToast({ title: "输入的字符非法", @@ -106,7 +101,6 @@ const EditModal: React.FC = ({ }); return; } else if (hasIllegal) { - console.log("hasIllegal") Taro.showToast({ title: "内容不能包含@<>/等无效字符", icon: "none", @@ -128,7 +122,6 @@ const EditModal: React.FC = ({ setValue(initialValue); setHasIllegal(false); setIsIllegal(false); - setCanEdit(true); onCancel(); }; @@ -176,12 +169,15 @@ const EditModal: React.FC = ({ confirmType="done" // autoFocus={true} onConfirm={handle_save} - onBlur={(e) => { e.preventDefault() }} + onBlur={(e) => { + e.preventDefault(); + }} /> maxLength && "un-valid" - }`} + className={`count_text ${ + value.length > maxLength && "un-valid" + }`} > {value.length}/{maxLength} @@ -208,8 +204,9 @@ const EditModal: React.FC = ({ /> maxLength && "un-valid" - }`} + className={`count_text ${ + value.length > maxLength && "un-valid" + }`} > {value.length}/{maxLength} diff --git a/src/components/UserInfo/index.tsx b/src/components/UserInfo/index.tsx index 9679aee..5f61347 100644 --- a/src/components/UserInfo/index.tsx +++ b/src/components/UserInfo/index.tsx @@ -6,7 +6,7 @@ import "./index.scss"; import { EditModal } from "@/components"; import { UserService, PickerOption } from "@/services/userService"; import { PopupPicker } from "@/components/Picker/index"; -import { useUserActions } from "@/store/userStore"; +import { useUserActions, useNicknameChangeStatus } from "@/store/userStore"; import { UserInfoType } from "@/services/userService"; import { useCities, useProfessions } from "@/store/pickerOptionsStore"; import { formatNtrpDisplay } from "@/utils/helper"; @@ -77,8 +77,9 @@ const UserInfoCardComponent: React.FC = ({ set_user_info, onTab, }) => { + const nickname_change_status = useNicknameChangeStatus(); const { setShowGuideBar } = useGlobalState(); - const { updateUserInfo } = useUserActions(); + const { updateUserInfo, updateNickname } = useUserActions(); const [ntrpTested, setNtrpTested] = useState(false); // 使用 useRef 记录上一次的 user_info,只在真正变化时打印 @@ -185,6 +186,13 @@ const UserInfoCardComponent: React.FC = ({ } if (field === "nickname") { if (!is_current_user) return; + if (!nickname_change_status.can_change) { + return Taro.showToast({ + title: `30天内仅可修改4次昵称,${nickname_change_status.next_period_start_time}后可修改`, + icon: "none", + duration: 2000, + }); + } // 手动输入 setShowGuideBar(false); setEditingField(field); @@ -200,10 +208,10 @@ const UserInfoCardComponent: React.FC = ({ try { // 调用更新用户信息接口,只传递修改的字段 const update_data = { [editing_field]: value }; - await UserService.update_user_info(update_data); - - await updateUserInfo({ [editing_field]: value }); - + // await UserService.update_user_info(update_data); + editing_field === "nickname" + ? await updateNickname(value) + : await updateUserInfo(update_data); set_form_data((prev) => { return { ...prev, ...update_data }; }); @@ -607,7 +615,7 @@ const UserInfoCardComponent: React.FC = ({ onCancel={handle_edit_modal_cancel} validationMessage={ editing_field === "nickname" - ? "请填写 2-24 个字符,不包括 @<>/等无效字符。30 天内可修改 4 次昵称,12.5 前还可修改 4 次。" + ? `请填写 2-24 个字符,不包括 @<>/等无效字符。30 天内可修改 4 次昵称,${nickname_change_status.next_period_start_time} 前还可修改 ${nickname_change_status.remaining_count} 次。` : "请填写 2-100 个字符" } /> diff --git a/src/home_pages/index.tsx b/src/home_pages/index.tsx index 48bf7fb..7af912a 100644 --- a/src/home_pages/index.tsx +++ b/src/home_pages/index.tsx @@ -1,44 +1,42 @@ -import React, { useEffect } from 'react'; -import { View, } from '@tarojs/components'; -import { silentLogin } from '@/services/loginService'; -import { useUserActions } from '@/store/userStore'; -import Taro from '@tarojs/taro'; +import React, { useEffect } from "react"; +import { View } from "@tarojs/components"; +import { silentLogin } from "@/services/loginService"; +import { useUserActions } from "@/store/userStore"; +import Taro from "@tarojs/taro"; import "./index.scss"; const HomePage: React.FC = () => { - const { fetchUserInfo } = useUserActions(); + const { fetchUserInfo, checkNicknameChangeStatus } = useUserActions(); + useEffect(() => { + const handleLoginRedirect = async () => { + // 先执行静默登录,然后再跳转 + try { + console.log("开始静默登录..."); + const loginResult = await silentLogin(); + console.log("静默登录结果:", loginResult); + if (loginResult.success) { + // 静默登录成功,获取用户信息 + fetchUserInfo().catch((error) => { + console.error("获取用户信息失败:", error); + }); + checkNicknameChangeStatus().catch((error) => { + console.error("检查昵称变更状态失败:", error); + }); + } + } catch (error) { + console.error("静默登录失败:", error); + // 静默登录失败不影响使用 + } - useEffect(() => { - const handleLoginRedirect = async () => { - // 先执行静默登录,然后再跳转 - try { - console.log('开始静默登录...'); - const loginResult = await silentLogin(); - console.log('静默登录结果:', loginResult); - if (loginResult.success) { - // 静默登录成功,获取用户信息 - fetchUserInfo().catch((error) => { - console.error('获取用户信息失败:', error); - }); - } - } catch (error) { - console.error('静默登录失败:', error); - // 静默登录失败不影响使用 - } - - // 无论静默登录是否成功,都跳转到主页面 - Taro.redirectTo({ url: '/main_pages/index' }); - }; + // 无论静默登录是否成功,都跳转到主页面 + Taro.redirectTo({ url: "/main_pages/index" }); + }; - handleLoginRedirect(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // fetchUserInfo 是稳定的函数引用,不需要加入依赖项 + handleLoginRedirect(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // fetchUserInfo 是稳定的函数引用,不需要加入依赖项 - return ( - + return ; +}; - - ); -} - -export default HomePage; \ No newline at end of file +export default HomePage; diff --git a/src/services/userService.ts b/src/services/userService.ts index c5f5d4c..77cdad5 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -90,6 +90,14 @@ export interface UserInfoType { ongoing_games?: string[]; } +export interface NicknameChangeStatus { + can_change: boolean; + remaining_count: number; + period_start_time: string; + next_period_start_time: string; + days_until_next_period: number; +} + // 后端球局数据接口 interface BackendGameData { id: number; @@ -681,6 +689,33 @@ export const fetchUserProfile = async (): Promise< } }; +// 获取昵称修改状态 +export const checkNicknameChangeStatus = async (): Promise< + ApiResponse +> => { + try { + const response = await httpService.post( + "/user/check_nickname_change_status" + ); + return response; + } catch (error) { + console.error("获取昵称修改状态失败:", error); + throw error; + } +}; +// 修改昵称 +export const updateNickname = async (nickname: string) => { + try { + const response = await httpService.post("/user/update_nickname", { + nickname, + }); + return response; + } catch (error) { + console.error("昵称修改失败:", error); + throw error; + } +}; + // 更新用户信息 export const updateUserProfile = async (payload: Partial) => { try { diff --git a/src/store/userStore.ts b/src/store/userStore.ts index 5c22a0e..df712a7 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -3,12 +3,18 @@ import { fetchUserProfile, updateUserProfile, UserInfoType, + checkNicknameChangeStatus as checkNicknameChangeStatusApi, + NicknameChangeStatus, + updateNickname as updateNicknameApi, } from "@/services/userService"; export interface UserState { user: UserInfoType | {}; fetchUserInfo: () => Promise; updateUserInfo: (userInfo: Partial) => void; + nicknameChangeStatus: Partial; + checkNicknameChangeStatus: () => void; + updateNickname: (nickname: string) => void; } // 请求锁,防止重复请求 @@ -20,7 +26,7 @@ const fetchUserInfoWithLock = async (set) => { if (fetchingUserInfo && fetchUserInfoPromise) { return fetchUserInfoPromise; } - + fetchingUserInfo = true; fetchUserInfoPromise = (async () => { try { @@ -35,7 +41,7 @@ const fetchUserInfoWithLock = async (set) => { fetchUserInfoPromise = null; } })(); - + return fetchUserInfoPromise; }; @@ -58,12 +64,36 @@ export const useUser = create()((set) => ({ throw error; } }, + nicknameChangeStatus: {}, + checkNicknameChangeStatus: async () => { + try { + const res = await checkNicknameChangeStatusApi(); + set({ nicknameChangeStatus: res.data }); + } catch (error) { + console.error("检查昵称变更状态失败:", error); + } + }, + updateNickname: async (nickname) => { + try { + await updateNicknameApi(nickname); + await useUser.getState().checkNicknameChangeStatus(); + set((state) => ({ + user: { ...state.user, nickname }, + })); + } catch (error) { + console.error("更新用户昵称失败:", error); + } + }, })); export const useUserInfo = () => useUser((state) => state.user); +export const useNicknameChangeStatus = () => + useUser((state) => state.nicknameChangeStatus); export const useUserActions = () => useUser((state) => ({ fetchUserInfo: state.fetchUserInfo, updateUserInfo: state.updateUserInfo, + checkNicknameChangeStatus: state.checkNicknameChangeStatus, + updateNickname: state.updateNickname, })); diff --git a/src/user_pages/edit/index.tsx b/src/user_pages/edit/index.tsx index b820a43..a109011 100644 --- a/src/user_pages/edit/index.tsx +++ b/src/user_pages/edit/index.tsx @@ -9,16 +9,21 @@ import { convert_db_gender_to_display } from "@/utils/genderUtils"; import { EditModal, GeneralNavbar } from "@/components"; // import img from "@/config/images"; import CommonDialog from "@/components/CommonDialog"; -import { useUserActions, useUserInfo } from "@/store/userStore"; +import { + useUserActions, + useUserInfo, + useNicknameChangeStatus, +} from "@/store/userStore"; import { UserInfoType } from "@/services/userService"; import { useCities, useProfessions } from "@/store/pickerOptionsStore"; import { handleCustomerService } from "@/services/userService"; import evaluateService from "@/services/evaluateService"; const EditProfilePage: React.FC = () => { - const { updateUserInfo } = useUserActions(); + const { updateUserInfo, updateNickname } = useUserActions(); // 直接从store获取用户信息,确保响应式更新 const user_info = useUserInfo(); + const nickname_change_status = useNicknameChangeStatus(); // 表单状态,基于store中的用户信息初始化 const getInitialFormData = () => { @@ -190,6 +195,13 @@ const EditProfilePage: React.FC = () => { return; } if (field === "nickname") { + if (!nickname_change_status.can_change) { + return Taro.showToast({ + title: `30天内仅可修改4次昵称,${nickname_change_status.next_period_start_time}后可修改`, + icon: "none", + duration: 2000, + }); + } // 手动输入 setEditingField(field); setEditModalVisible(true); @@ -211,8 +223,9 @@ const EditProfilePage: React.FC = () => { } // 调用更新用户信息接口,只传递修改的字段 const update_data = { [editing_field]: value }; - await updateUserInfo(update_data); - + editing_field === "nickname" + ? await updateNickname(value) + : await updateUserInfo(update_data); // 更新表单状态(store会自动更新) setFormData((prev) => ({ ...prev, [editing_field]: value })); @@ -811,7 +824,7 @@ const EditProfilePage: React.FC = () => { onCancel={handle_edit_modal_cancel} validationMessage={ editing_field === "nickname" - ? "请填写 2-24 个字符,不包括 @<>/等无效字符。30 天内可修改 4 次昵称,12.5 前还可修改 4 次。" + ? `请填写 2-24 个字符,不包括 @<>/等无效字符。30 天内可修改 4 次昵称,${nickname_change_status.next_period_start_time} 前还可修改 ${nickname_change_status.remaining_count} 次。` : "请填写 2-100 个字符" } />