优化获取昵称修改状态的时机

This commit is contained in:
2025-12-03 15:44:41 +08:00
parent 7c1a1fafc1
commit b77b4b0536
9 changed files with 160 additions and 128 deletions

View File

@@ -4,7 +4,7 @@
position: fixed;
z-index: 9999 !important;
&:global(.nut-popup-bottom.nut-popup-round) {
border-radius: 20px 20px 0 0!important;
border-radius: 20px 20px 0 0 !important;
}
.common-popup__drag-handle-container {
position: position;
@@ -22,8 +22,8 @@
display: flex;
justify-content: center;
align-items: flex-start;
&::before{
content: '';
&::before {
content: "";
width: 32px;
height: 4px;
background-color: rgba(22, 24, 35, 0.2);
@@ -105,7 +105,7 @@
padding: 8px 10px 0 10px;
display: flex;
gap: 8px;
background: #fff;
background: #fafafa;
padding-bottom: max(10px, env(safe-area-inset-bottom));
}

View File

@@ -11,7 +11,7 @@ interface EditModalProps {
placeholder: string;
initialValue: string;
maxLength: number;
invalidCharacters: string;
invalidCharacters: RegExp | null;
onSave: (value: string) => void;
onCancel: () => void;
validationMessage?: string;
@@ -24,7 +24,7 @@ const EditModal: React.FC<EditModalProps> = ({
placeholder,
initialValue,
maxLength,
invalidCharacters = "",
invalidCharacters = null,
onSave,
onCancel,
validationMessage,
@@ -63,18 +63,13 @@ const EditModal: React.FC<EditModalProps> = ({
}
}, [visible, initialValue]);
const createExcludeRegex = (chars: string) => {
const escapedChars = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = `[${escapedChars}]`;
return new RegExp(pattern);
};
const handle_input_change = (e: any) => {
const new_value = e.detail.value;
setValue(new_value);
let ishasIllegal = false;
if (type === "nickname") {
ishasIllegal = createExcludeRegex(invalidCharacters).test(new_value);
setHasIllegal(ishasIllegal);
if (type === "nickname" && invalidCharacters) {
ishasIllegal = invalidCharacters.test(new_value);
setHasIllegal(!ishasIllegal);
}
const illegal =
/\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|EXEC|DECLARE)\b|('|--|\/\*|\*\/|;|#)|(=|'|"|`|\\|\|\|&&)|\bOR\s+['"]?[\w]+['"]?\s*=\s*['"]?[\w]+['"]?|\bUNION\s+SELECT\b|\bDROP\s+TABLE\b|\bINSERT\s+INTO\b|\bUPDATE\s+[\w]+\s+SET\b|\bDELETE\s+FROM\b/i.test(

View File

@@ -610,7 +610,11 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
}
initialValue={form_data[editing_field as keyof typeof form_data] || ""}
maxLength={editing_field === "nickname" ? 20 : 100}
invalidCharacters={editing_field === "nickname" ? "@<>/" : ""}
invalidCharacters={
editing_field === "nickname"
? /^[\u4e00-\u9fa5a-zA-Z0-9_\-\.\(\)\s]*$/
: null
}
onSave={handle_edit_modal_save}
onCancel={handle_edit_modal_cancel}
validationMessage={

View File

@@ -20,7 +20,7 @@ const LoginPage: React.FC = () => {
const {
params: { redirect },
} = useRouter();
const { fetchUserInfo } = useUserActions();
const { fetchUserInfo, checkNicknameChangeStatus } = useUserActions();
// 执行微信登录的核心逻辑
const execute_wechat_login = async (e: any) => {
@@ -41,6 +41,7 @@ const LoginPage: React.FC = () => {
if (response.success) {
save_login_state(response.token!, response.user_info!);
fetchUserInfo();
checkNicknameChangeStatus();
setTimeout(() => {
if (redirect) {

View File

@@ -12,7 +12,7 @@ import ListPageContent from "./components/ListPageContent";
import MessagePageContent from "./components/MessagePageContent";
import MyselfPageContent from "./components/MyselfPageContent";
import "./index.scss";
import FamilyContext from '@/context';
import FamilyContext from "@/context";
type TabType = "list" | "message" | "personal";
@@ -22,14 +22,17 @@ const MainPage: React.FC = () => {
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
const [isFilterPopupVisible, setIsFilterPopupVisible] = useState(false);
const [isShowInputCustomerNavBar, setIsShowInputCustomerNavBar] = useState(false);
const [listPageScrollToTopTrigger, setListPageScrollToTopTrigger] = useState(0);
const [isShowInputCustomerNavBar, setIsShowInputCustomerNavBar] =
useState(false);
const [listPageScrollToTopTrigger, setListPageScrollToTopTrigger] =
useState(0);
const [showAuthError, setShowAuthError] = useState(false);
const [authErrorMessage, setAuthErrorMessage] = useState('');
const [authErrorMessage, setAuthErrorMessage] = useState("");
const { fetchUserInfo } = useUserActions();
const { fetchUserInfo, checkNicknameChangeStatus } = useUserActions();
// 从 store 获取 GuideBar 相关状态和方法
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } = useGlobalState();
const { showGuideBar, guideBarZIndex, setShowGuideBar, setGuideBarZIndex } =
useGlobalState();
// 初始化:自动微信授权并获取用户信息
useEffect(() => {
@@ -38,21 +41,21 @@ const MainPage: React.FC = () => {
if (!hasValidToken) {
try {
console.log('开始微信授权...');
console.log("开始微信授权...");
const loginRes = await wechat_auth_login();
if (loginRes.success && loginRes.token) {
save_login_state(loginRes.token, loginRes.user_info);
console.log('微信授权成功');
console.log("微信授权成功");
} else {
// 显示错误提示
setAuthErrorMessage(loginRes.message || '微信授权失败');
setAuthErrorMessage(loginRes.message || "微信授权失败");
setShowAuthError(true);
return;
}
} catch (error) {
console.error('微信授权异常:', error);
setAuthErrorMessage('微信授权失败,请重试');
console.error("微信授权异常:", error);
setAuthErrorMessage("微信授权失败,请重试");
setShowAuthError(true);
return;
}
@@ -62,8 +65,9 @@ const MainPage: React.FC = () => {
if (tokenManager.hasValidToken()) {
try {
await fetchUserInfo();
await checkNicknameChangeStatus();
} catch (error) {
console.error('获取用户信息失败:', error);
console.error("获取用户信息失败:", error);
}
}
};
@@ -75,29 +79,32 @@ const MainPage: React.FC = () => {
useEffect(() => {
if (showAuthError) {
Taro.showModal({
title: '授权失败',
title: "授权失败",
content: `${authErrorMessage}\n\n请重启小程序后重试`,
showCancel: false,
confirmText: '我知道了',
confirmText: "我知道了",
success: () => {
setShowAuthError(false);
}
},
});
}
}, [showAuthError, authErrorMessage]);
// 处理标签切换
const handleTabChange = useCallback((code: string) => {
if (code === currentTab) {
return;
}
setCurrentTab(code as TabType);
// 切换标签时滚动到顶部
(Taro as any).pageScrollTo({
scrollTop: 0,
duration: 300,
});
}, [currentTab]);
const handleTabChange = useCallback(
(code: string) => {
if (code === currentTab) {
return;
}
setCurrentTab(code as TabType);
// 切换标签时滚动到顶部
(Taro as any).pageScrollTo({
scrollTop: 0,
duration: 300,
});
},
[currentTab]
);
// 处理发布菜单显示/隐藏
const handlePublishMenuVisibleChange = useCallback((visible: boolean) => {
@@ -120,28 +127,31 @@ const MainPage: React.FC = () => {
}, []);
// 处理列表页导航状态变化
const handleListNavStateChange = useCallback((state: {
isShowInputCustomerNavBar?: boolean;
isDistanceFilterVisible?: boolean;
isCityPickerVisible?: boolean;
}) => {
if (state.isShowInputCustomerNavBar !== undefined) {
setIsShowInputCustomerNavBar(state.isShowInputCustomerNavBar);
}
if (state.isDistanceFilterVisible !== undefined) {
setIsDistanceFilterVisible(state.isDistanceFilterVisible);
}
if (state.isCityPickerVisible !== undefined) {
setIsCityPickerVisible(state.isCityPickerVisible);
}
}, []);
const handleListNavStateChange = useCallback(
(state: {
isShowInputCustomerNavBar?: boolean;
isDistanceFilterVisible?: boolean;
isCityPickerVisible?: boolean;
}) => {
if (state.isShowInputCustomerNavBar !== undefined) {
setIsShowInputCustomerNavBar(state.isShowInputCustomerNavBar);
}
if (state.isDistanceFilterVisible !== undefined) {
setIsDistanceFilterVisible(state.isDistanceFilterVisible);
}
if (state.isCityPickerVisible !== undefined) {
setIsCityPickerVisible(state.isCityPickerVisible);
}
},
[]
);
// 滚动到顶部
const scrollToTop = useCallback(() => {
// 如果当前是列表页,触发列表页内部滚动
if (currentTab === "list") {
// 通过状态变化触发 ListPageContent 内部滚动
setListPageScrollToTopTrigger(prev => prev + 1);
setListPageScrollToTopTrigger((prev) => prev + 1);
} else {
// 其他页面使用 pageScrollTo
(Taro as any).pageScrollTo({
@@ -154,13 +164,22 @@ const MainPage: React.FC = () => {
// 动态控制 GuideBar 的 z-index
useEffect(() => {
if (isPublishMenuVisible) {
setGuideBarZIndex('high');
} else if (isDistanceFilterVisible || isCityPickerVisible || isFilterPopupVisible) {
setGuideBarZIndex('low');
setGuideBarZIndex("high");
} else if (
isDistanceFilterVisible ||
isCityPickerVisible ||
isFilterPopupVisible
) {
setGuideBarZIndex("low");
} else {
setGuideBarZIndex('high');
setGuideBarZIndex("high");
}
}, [isPublishMenuVisible, isDistanceFilterVisible, isCityPickerVisible, isFilterPopupVisible]);
}, [
isPublishMenuVisible,
isDistanceFilterVisible,
isCityPickerVisible,
isFilterPopupVisible,
]);
// 渲染自定义导航栏(参考原始页面的实现)
const renderCustomNavbar = () => {
@@ -190,21 +209,15 @@ const MainPage: React.FC = () => {
);
} else if (currentTab === "personal") {
// 我的页:使用 GeneralNavbar 显示标题
return (
<GeneralNavbar
title=""
titlePosition="left"
showBack={false}
/>
);
return <GeneralNavbar title="" titlePosition="left" showBack={false} />;
}
return null;
};
const handleGrandchildTrigger = (value) => {
console.log('[MainPage] handleGrandchildTrigger called with:', value);
console.log("[MainPage] handleGrandchildTrigger called with:", value);
setShowGuideBar(!value);
}
};
return (
<FamilyContext.Provider value={{ handleGrandchildTrigger }}>
@@ -241,21 +254,21 @@ const MainPage: React.FC = () => {
</View>
{/* 底部导航栏 */}
{
showGuideBar ?
<GuideBar
currentPage={currentTab}
guideBarClassName={guideBarZIndex === 'low' ? 'guide-bar-low-z-index' : 'guide-bar-high-z-index'}
onTabChange={handleTabChange}
onPublishMenuVisibleChange={handlePublishMenuVisibleChange}
/> :
null
}
{showGuideBar ? (
<GuideBar
currentPage={currentTab}
guideBarClassName={
guideBarZIndex === "low"
? "guide-bar-low-z-index"
: "guide-bar-high-z-index"
}
onTabChange={handleTabChange}
onPublishMenuVisibleChange={handlePublishMenuVisibleChange}
/>
) : null}
</View>
</FamilyContext.Provider>
);
};
export default MainPage;

View File

@@ -82,7 +82,7 @@ export interface UserInfoType {
// 微信授权登录
// phone_code: 可选参数,如果提供则绑定手机号,否则只进行微信授权
export const wechat_auth_login = async (
phone_code?: string,
phone_code?: string
): Promise<LoginResponse> => {
try {
// 先进行微信登录获取code
@@ -144,7 +144,7 @@ export interface ChangePhoneParams {
// 手机号验证码登录
export const phone_auth_login = async (
params: PhoneLoginParams,
params: PhoneLoginParams
): Promise<LoginResponse> => {
try {
// 使用 httpService 调用验证验证码接口
@@ -158,6 +158,7 @@ export const phone_auth_login = async (
// 登录成功后,更新用户信息到 store
try {
await useUser.getState().fetchUserInfo();
await useUser.getState().checkNicknameChangeStatus();
} catch (error) {
console.error("更新用户信息到 store 失败:", error);
}
@@ -216,7 +217,7 @@ export const send_sms_code = async (phone: string): Promise<SmsResponse> => {
// 验证短信验证码
export const verify_sms_code = async (
phone: string,
code: string,
code: string
): Promise<VerifyCodeResponse> => {
try {
const response = await httpService.post("user/sms/verify", {
@@ -378,8 +379,6 @@ export const refresh_login_status = async (): Promise<boolean> => {
}
};
// 更新用户手机号
export const updateUserPhone = async (payload: ChangePhoneParams) => {
try {
@@ -389,13 +388,17 @@ export const updateUserPhone = async (payload: ChangePhoneParams) => {
console.error("更新用户手机号失败:", error);
throw error;
}
}
};
// 获取指定用户信息
export const getUserInfoById = async (id) => {
try {
const response = await httpService.post("/user/detail_by_id", { id }, {
showLoading: false,
},);
const response = await httpService.post(
"/user/detail_by_id",
{ id },
{
showLoading: false,
}
);
return response;
} catch (error) {
console.error("获取用户信息失败:", error);
@@ -436,13 +439,13 @@ let silentLoginPromise: Promise<LoginResponse> | null = null;
export const silentLogin = async (): Promise<LoginResponse> => {
// 如果已经有正在进行的静默登录,直接返回该 Promise
if (silentLoginPromise) {
console.log('静默登录正在进行中,等待结果...');
console.log("静默登录正在进行中,等待结果...");
return silentLoginPromise;
}
// 先检查是否已经登录
if (check_login_status()) {
console.log('已登录,跳过静默登录');
console.log("已登录,跳过静默登录");
return {
success: true,
message: "已登录",
@@ -454,13 +457,13 @@ export const silentLogin = async (): Promise<LoginResponse> => {
// 创建静默登录 Promise
silentLoginPromise = (async (): Promise<LoginResponse> => {
try {
console.log('开始执行静默登录...');
console.log("开始执行静默登录...");
// 调用微信登录获取code
const login_result = await Taro.login();
console.log('微信登录结果:', login_result);
console.log("微信登录结果:", login_result);
if (!login_result.code) {
console.error('微信登录失败未获取到code');
console.error("微信登录失败未获取到code");
return {
success: false,
message: "微信登录失败",
@@ -468,15 +471,19 @@ export const silentLogin = async (): Promise<LoginResponse> => {
}
// 调用微信授权接口,不传 phone_code静默登录
console.log('调用后端接口进行静默登录...');
const auth_response = await httpService.post("user/wx_auth", {
code: login_result.code,
// 不传 phone_code实现静默登录
}, {
showLoading: false, // 静默登录不显示loading
});
console.log("调用后端接口进行静默登录...");
const auth_response = await httpService.post(
"user/wx_auth",
{
code: login_result.code,
// 不传 phone_code实现静默登录
},
{
showLoading: false, // 静默登录不显示loading
}
);
console.log('后端接口响应:', auth_response);
console.log("后端接口响应:", auth_response);
if (auth_response.code === 0) {
const token = auth_response.data?.token || "";
@@ -484,11 +491,11 @@ export const silentLogin = async (): Promise<LoginResponse> => {
// 保存登录状态
if (token && user_info) {
console.log('保存登录状态...');
console.log("保存登录状态...");
save_login_state(token, user_info);
console.log('静默登录成功,已保存登录状态');
console.log("静默登录成功,已保存登录状态");
} else {
console.warn('静默登录成功但token或user_info为空');
console.warn("静默登录成功但token或user_info为空");
}
return {
@@ -498,7 +505,7 @@ export const silentLogin = async (): Promise<LoginResponse> => {
user_info,
};
} else {
console.error('静默登录失败:', auth_response.message);
console.error("静默登录失败:", auth_response.message);
return {
success: false,
message: auth_response.message || "静默登录失败",
@@ -517,4 +524,4 @@ export const silentLogin = async (): Promise<LoginResponse> => {
})();
return silentLoginPromise;
};
};

View File

@@ -819,7 +819,11 @@ const EditProfilePage: React.FC = () => {
}
initialValue={form_data[editing_field as keyof typeof form_data] || ""}
maxLength={editing_field === "nickname" ? 24 : 100}
invalidCharacters={editing_field === "nickname" ? "@<>/" : ""}
invalidCharacters={
editing_field === "nickname"
? /^[\u4e00-\u9fa5a-zA-Z0-9_\-\.\(\)\s]*$/
: null
}
onSave={handle_edit_modal_save}
onCancel={handle_edit_modal_cancel}
validationMessage={

View File

@@ -156,12 +156,11 @@
.popup_text {
font-family: DingTalk JinBuTi;
font-weight: 400;
font-weight: 600;
font-style: Regular;
font-size: 20px;
line-height: 16px;
margin: 20px 0;
font-style: italic;
.integer {
font-size: 36px;
}

View File

@@ -1,4 +1,8 @@
import { check_login_status, get_user_token, silentLogin } from "@/services/loginService";
import {
check_login_status,
get_user_token,
silentLogin,
} from "@/services/loginService";
import { useUser } from "@/store/userStore";
import tokenManager from "@/utils/tokenManager";
@@ -15,7 +19,7 @@ const initUserAuthCore = async (): Promise<void> => {
// 如果已经登录且有用户信息,跳过
const login_status = check_login_status();
const { user, fetchUserInfo } = useUser.getState();
const { user, fetchUserInfo, checkNicknameChangeStatus } = useUser.getState();
if (login_status && user && Object.keys(user).length > 0) {
return Promise.resolve();
}
@@ -27,8 +31,9 @@ const initUserAuthCore = async (): Promise<void> => {
// 已登录,获取用户信息
try {
await fetchUserInfo();
await checkNicknameChangeStatus();
} catch (error) {
console.error('获取用户信息失败:', error);
console.error("获取用户信息失败:", error);
}
} else {
// 未登录,尝试静默登录
@@ -37,14 +42,15 @@ const initUserAuthCore = async (): Promise<void> => {
if (loginResult.success) {
// 静默登录成功,获取用户信息
await fetchUserInfo();
await checkNicknameChangeStatus();
}
} catch (error) {
console.error('静默登录失败:', error);
console.error("静默登录失败:", error);
// 静默登录失败不影响使用
}
}
} catch (error) {
console.error('初始化用户授权失败:', error);
console.error("初始化用户授权失败:", error);
} finally {
isInitializingAuth = false;
authInitPromise = null;
@@ -60,7 +66,7 @@ export const waitForAuthInit = async (): Promise<void> => {
if (tokenManager.hasValidToken() || get_user_token()) {
return Promise.resolve();
}
// 如果正在初始化,等待完成
if (authInitPromise) {
await authInitPromise;
@@ -69,7 +75,7 @@ export const waitForAuthInit = async (): Promise<void> => {
return;
}
}
// 检查是否已经登录且有用户信息
const login_status = check_login_status();
const { user } = useUser.getState();
@@ -79,22 +85,26 @@ export const waitForAuthInit = async (): Promise<void> => {
return Promise.resolve();
}
}
// 触发初始化
const promise = initUserAuthCore();
await promise;
// 等待 token 真正存在(最多等待 2 秒)
let retryCount = 0;
const maxRetries = 20; // 20 * 100ms = 2秒
while (retryCount < maxRetries && !tokenManager.hasValidToken() && !get_user_token()) {
await new Promise(resolve => setTimeout(resolve, 100));
while (
retryCount < maxRetries &&
!tokenManager.hasValidToken() &&
!get_user_token()
) {
await new Promise((resolve) => setTimeout(resolve, 100));
retryCount++;
}
// 如果还是没有 token记录警告但继续执行
if (!tokenManager.hasValidToken() && !get_user_token()) {
console.warn('等待静默登录完成,但未获取到 token');
console.warn("等待静默登录完成,但未获取到 token");
}
};
@@ -102,4 +112,3 @@ export const waitForAuthInit = async (): Promise<void> => {
export const initUserAuth = () => {
return initUserAuthCore();
};