获取昵称修改状态、优化昵称修改逻辑

This commit is contained in:
2025-12-03 11:53:36 +08:00
parent 159b4ab1d2
commit 7c1a1fafc1
6 changed files with 146 additions and 65 deletions

View File

@@ -33,7 +33,6 @@ const EditModal: React.FC<EditModalProps> = ({
const [isValid, setIsValid] = useState(true); const [isValid, setIsValid] = useState(true);
const [isIllegal, setIsIllegal] = useState(false); const [isIllegal, setIsIllegal] = useState(false);
const [hasIllegal, setHasIllegal] = useState(false); const [hasIllegal, setHasIllegal] = useState(false);
const [canEdit, setCanEdit] = useState(true);
// 使用全局键盘状态 // 使用全局键盘状态
const { const {
keyboardHeight, keyboardHeight,
@@ -89,15 +88,11 @@ const EditModal: React.FC<EditModalProps> = ({
value.length >= 2 && value.length >= 2 &&
value.length <= maxLength && value.length <= maxLength &&
!hasIllegal && !hasIllegal &&
!isIllegal && !isIllegal;
canEdit;
setIsValid(valid); setIsValid(valid);
}, [ }, [value, hasIllegal, isIllegal]);
value, hasIllegal, isIllegal, canEdit
])
const handle_save = () => { const handle_save = () => {
console.log("savexxxxxxx", isIllegal, hasIllegal, !isValid)
if (isIllegal) { if (isIllegal) {
Taro.showToast({ Taro.showToast({
title: "输入的字符非法", title: "输入的字符非法",
@@ -106,7 +101,6 @@ const EditModal: React.FC<EditModalProps> = ({
}); });
return; return;
} else if (hasIllegal) { } else if (hasIllegal) {
console.log("hasIllegal")
Taro.showToast({ Taro.showToast({
title: "内容不能包含@<>/等无效字符", title: "内容不能包含@<>/等无效字符",
icon: "none", icon: "none",
@@ -128,7 +122,6 @@ const EditModal: React.FC<EditModalProps> = ({
setValue(initialValue); setValue(initialValue);
setHasIllegal(false); setHasIllegal(false);
setIsIllegal(false); setIsIllegal(false);
setCanEdit(true);
onCancel(); onCancel();
}; };
@@ -176,12 +169,15 @@ const EditModal: React.FC<EditModalProps> = ({
confirmType="done" confirmType="done"
// autoFocus={true} // autoFocus={true}
onConfirm={handle_save} onConfirm={handle_save}
onBlur={(e) => { e.preventDefault() }} onBlur={(e) => {
e.preventDefault();
}}
/> />
<View className="char_count"> <View className="char_count">
<Text <Text
className={`count_text ${value.length > maxLength && "un-valid" className={`count_text ${
}`} value.length > maxLength && "un-valid"
}`}
> >
{value.length}/{maxLength} {value.length}/{maxLength}
</Text> </Text>
@@ -208,8 +204,9 @@ const EditModal: React.FC<EditModalProps> = ({
/> />
<View className="char_count"> <View className="char_count">
<Text <Text
className={`count_text ${value.length > maxLength && "un-valid" className={`count_text ${
}`} value.length > maxLength && "un-valid"
}`}
> >
{value.length}/{maxLength} {value.length}/{maxLength}
</Text> </Text>

View File

@@ -6,7 +6,7 @@ import "./index.scss";
import { EditModal } from "@/components"; import { EditModal } from "@/components";
import { UserService, PickerOption } from "@/services/userService"; import { UserService, PickerOption } from "@/services/userService";
import { PopupPicker } from "@/components/Picker/index"; import { PopupPicker } from "@/components/Picker/index";
import { useUserActions } from "@/store/userStore"; import { useUserActions, useNicknameChangeStatus } from "@/store/userStore";
import { UserInfoType } from "@/services/userService"; import { UserInfoType } from "@/services/userService";
import { useCities, useProfessions } from "@/store/pickerOptionsStore"; import { useCities, useProfessions } from "@/store/pickerOptionsStore";
import { formatNtrpDisplay } from "@/utils/helper"; import { formatNtrpDisplay } from "@/utils/helper";
@@ -77,8 +77,9 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
set_user_info, set_user_info,
onTab, onTab,
}) => { }) => {
const nickname_change_status = useNicknameChangeStatus();
const { setShowGuideBar } = useGlobalState(); const { setShowGuideBar } = useGlobalState();
const { updateUserInfo } = useUserActions(); const { updateUserInfo, updateNickname } = useUserActions();
const [ntrpTested, setNtrpTested] = useState(false); const [ntrpTested, setNtrpTested] = useState(false);
// 使用 useRef 记录上一次的 user_info只在真正变化时打印 // 使用 useRef 记录上一次的 user_info只在真正变化时打印
@@ -185,6 +186,13 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
} }
if (field === "nickname") { if (field === "nickname") {
if (!is_current_user) return; 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); setShowGuideBar(false);
setEditingField(field); setEditingField(field);
@@ -200,10 +208,10 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
try { try {
// 调用更新用户信息接口,只传递修改的字段 // 调用更新用户信息接口,只传递修改的字段
const update_data = { [editing_field]: value }; const update_data = { [editing_field]: value };
await UserService.update_user_info(update_data); // await UserService.update_user_info(update_data);
editing_field === "nickname"
await updateUserInfo({ [editing_field]: value }); ? await updateNickname(value)
: await updateUserInfo(update_data);
set_form_data((prev) => { set_form_data((prev) => {
return { ...prev, ...update_data }; return { ...prev, ...update_data };
}); });
@@ -607,7 +615,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
onCancel={handle_edit_modal_cancel} onCancel={handle_edit_modal_cancel}
validationMessage={ validationMessage={
editing_field === "nickname" 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 个字符" : "请填写 2-100 个字符"
} }
/> />

View File

@@ -1,44 +1,42 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { View, } from '@tarojs/components'; import { View } from "@tarojs/components";
import { silentLogin } from '@/services/loginService'; import { silentLogin } from "@/services/loginService";
import { useUserActions } from '@/store/userStore'; import { useUserActions } from "@/store/userStore";
import Taro from '@tarojs/taro'; import Taro from "@tarojs/taro";
import "./index.scss"; import "./index.scss";
const HomePage: React.FC = () => { 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 () => { Taro.redirectTo({ url: "/main_pages/index" });
// 先执行静默登录,然后再跳转 };
try {
console.log('开始静默登录...');
const loginResult = await silentLogin();
console.log('静默登录结果:', loginResult);
if (loginResult.success) {
// 静默登录成功,获取用户信息
fetchUserInfo().catch((error) => {
console.error('获取用户信息失败:', error);
});
}
} catch (error) {
console.error('静默登录失败:', error);
// 静默登录失败不影响使用
}
// 无论静默登录是否成功,都跳转到主页面 handleLoginRedirect();
Taro.redirectTo({ url: '/main_pages/index' }); // eslint-disable-next-line react-hooks/exhaustive-deps
}; }, []); // fetchUserInfo 是稳定的函数引用,不需要加入依赖项
handleLoginRedirect(); return <View className="home_page"></View>;
// eslint-disable-next-line react-hooks/exhaustive-deps };
}, []); // fetchUserInfo 是稳定的函数引用,不需要加入依赖项
return (
<View className="home_page">
</View>
);
}
export default HomePage; export default HomePage;

View File

@@ -90,6 +90,14 @@ export interface UserInfoType {
ongoing_games?: string[]; 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 { interface BackendGameData {
id: number; id: number;
@@ -681,6 +689,33 @@ export const fetchUserProfile = async (): Promise<
} }
}; };
// 获取昵称修改状态
export const checkNicknameChangeStatus = async (): Promise<
ApiResponse<NicknameChangeStatus>
> => {
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<UserInfoType>) => { export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
try { try {

View File

@@ -3,12 +3,18 @@ import {
fetchUserProfile, fetchUserProfile,
updateUserProfile, updateUserProfile,
UserInfoType, UserInfoType,
checkNicknameChangeStatus as checkNicknameChangeStatusApi,
NicknameChangeStatus,
updateNickname as updateNicknameApi,
} from "@/services/userService"; } from "@/services/userService";
export interface UserState { export interface UserState {
user: UserInfoType | {}; user: UserInfoType | {};
fetchUserInfo: () => Promise<UserInfoType | undefined>; fetchUserInfo: () => Promise<UserInfoType | undefined>;
updateUserInfo: (userInfo: Partial<UserInfoType>) => void; updateUserInfo: (userInfo: Partial<UserInfoType>) => void;
nicknameChangeStatus: Partial<NicknameChangeStatus>;
checkNicknameChangeStatus: () => void;
updateNickname: (nickname: string) => void;
} }
// 请求锁,防止重复请求 // 请求锁,防止重复请求
@@ -58,12 +64,36 @@ export const useUser = create<UserState>()((set) => ({
throw error; 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 useUserInfo = () => useUser((state) => state.user);
export const useNicknameChangeStatus = () =>
useUser((state) => state.nicknameChangeStatus);
export const useUserActions = () => export const useUserActions = () =>
useUser((state) => ({ useUser((state) => ({
fetchUserInfo: state.fetchUserInfo, fetchUserInfo: state.fetchUserInfo,
updateUserInfo: state.updateUserInfo, updateUserInfo: state.updateUserInfo,
checkNicknameChangeStatus: state.checkNicknameChangeStatus,
updateNickname: state.updateNickname,
})); }));

View File

@@ -9,16 +9,21 @@ import { convert_db_gender_to_display } from "@/utils/genderUtils";
import { EditModal, GeneralNavbar } from "@/components"; import { EditModal, GeneralNavbar } from "@/components";
// import img from "@/config/images"; // import img from "@/config/images";
import CommonDialog from "@/components/CommonDialog"; import CommonDialog from "@/components/CommonDialog";
import { useUserActions, useUserInfo } from "@/store/userStore"; import {
useUserActions,
useUserInfo,
useNicknameChangeStatus,
} from "@/store/userStore";
import { UserInfoType } from "@/services/userService"; import { UserInfoType } from "@/services/userService";
import { useCities, useProfessions } from "@/store/pickerOptionsStore"; import { useCities, useProfessions } from "@/store/pickerOptionsStore";
import { handleCustomerService } from "@/services/userService"; import { handleCustomerService } from "@/services/userService";
import evaluateService from "@/services/evaluateService"; import evaluateService from "@/services/evaluateService";
const EditProfilePage: React.FC = () => { const EditProfilePage: React.FC = () => {
const { updateUserInfo } = useUserActions(); const { updateUserInfo, updateNickname } = useUserActions();
// 直接从store获取用户信息确保响应式更新 // 直接从store获取用户信息确保响应式更新
const user_info = useUserInfo(); const user_info = useUserInfo();
const nickname_change_status = useNicknameChangeStatus();
// 表单状态基于store中的用户信息初始化 // 表单状态基于store中的用户信息初始化
const getInitialFormData = () => { const getInitialFormData = () => {
@@ -190,6 +195,13 @@ const EditProfilePage: React.FC = () => {
return; return;
} }
if (field === "nickname") { 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); setEditingField(field);
setEditModalVisible(true); setEditModalVisible(true);
@@ -211,8 +223,9 @@ const EditProfilePage: React.FC = () => {
} }
// 调用更新用户信息接口,只传递修改的字段 // 调用更新用户信息接口,只传递修改的字段
const update_data = { [editing_field]: value }; const update_data = { [editing_field]: value };
await updateUserInfo(update_data); editing_field === "nickname"
? await updateNickname(value)
: await updateUserInfo(update_data);
// 更新表单状态store会自动更新 // 更新表单状态store会自动更新
setFormData((prev) => ({ ...prev, [editing_field]: value })); setFormData((prev) => ({ ...prev, [editing_field]: value }));
@@ -811,7 +824,7 @@ const EditProfilePage: React.FC = () => {
onCancel={handle_edit_modal_cancel} onCancel={handle_edit_modal_cancel}
validationMessage={ validationMessage={
editing_field === "nickname" 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 个字符" : "请填写 2-100 个字符"
} }
/> />