Parcourir la source

feat: 接口401跳转逻辑调整,pricing页面用户信息加载和显示逻辑调整

BaiLuoYan il y a 3 mois
Parent
commit
64d9f82520

+ 2 - 1
src/config/request/authHeaderInterceptor.ts

@@ -63,7 +63,8 @@ export const authHeaderInterceptor: IRequestInterceptorAxios = (config: RequestC
     config.headers['X-Request-Sign'] = stringSha1(
         String(ts) + stringMd5(navigator.userAgent ?? '')
     );
-    if (config.requireToken !== true) return config;
+    if (config.requireToken === false) return config; // 默认所有接口都需要 token,如果接口不需要 token,需要显示设置 requireToken 为 false
+    // if (config.requireToken !== true) return config; // 默认所有接口都不需要 token,如果接口需要 token,需要显示设置 requireToken 为 true
 
     const data = getToken();
     const expires = (data?.accessExpires ?? 0) - currentUnixTimestamp() > 0;

+ 4 - 3
src/config/request/requestErrorConfig.ts

@@ -4,7 +4,7 @@ import axios from 'axios';
 import { ErrorShowType } from '@/defines';
 import { reportError } from '@/firebase';
 import { RequestConfig } from '@/utils/request/types';
-import { toLoginPage } from '@/utils/routerUtils';
+// import { toLoginPage } from '@/utils/routerUtils';
 
 /**
  * @name 错误处理
@@ -83,7 +83,8 @@ export const errorConfig: RequestConfig = {
                     const { errorMessage, errorCode } = errorInfo;
 
                     if (errorCode === 401) {
-                        toLoginPage();
+                        // 如果接口返回 401 错误,跳转到登录页
+                        // toLoginPage(); // 本项目没有登录页,所以不跳转
                         return;
                     }
                     switch (errorInfo.showType) {
@@ -112,7 +113,7 @@ export const errorConfig: RequestConfig = {
             } else if (error.response) {
                 // Axios 的错误
                 if (error.response.status === 401) {
-                    toLoginPage();
+                    // toLoginPage();
                     return;
                 }
 

+ 1 - 1
src/locales/en-US/pages.ts

@@ -23,7 +23,7 @@ export default {
         selectPayMethod: 'Select your payment method',
         userInfo: {
             account: 'Your Account',
-            planExpireDate: 'Plan Expire Date',
+            planExpireTime: 'Plan Expire Time',
         },
         planTag: {
             mostPopular: 'Most Popular',

+ 1 - 1
src/locales/fa-IR/pages.ts

@@ -23,7 +23,7 @@ export default {
         selectPayMethod: 'روش پرداخت خود را انتخاب کنید',
         userInfo: {
             account: 'حساب شما',
-            planExpireDate: 'تاریخ انقضا پلن',
+            planExpireTime: 'زمان انقضای پلن',
         },
         planTag: {
             mostPopular: 'بیشترین محبوبیت',

+ 1 - 1
src/locales/zh-CN/pages.ts

@@ -23,7 +23,7 @@ export default {
         selectPayMethod: '选择您的支付方式',
         userInfo: {
             account: '您的账户',
-            planExpireDate: '套餐到期日期',
+            planExpireTime: '套餐到期时间',
         },
         planTag: {
             mostPopular: '最受欢迎',

+ 15 - 3
src/models/userConfigModel.ts

@@ -1,7 +1,13 @@
 import { useState, useCallback } from 'react';
 
+import { userKey } from '@/utils/authUtils';
+import { createLocalTools } from '@/utils/localUtils';
+import { currentUnixTimestamp } from '@/utils/timeUtils';
+
 import { createModel } from '../utils/model/createModel';
 
+const ls = createLocalTools();
+
 interface UserConfigState {
     userConfig: API.UserInfo | null;
 }
@@ -10,10 +16,16 @@ interface UserConfigModel extends UserConfigState {
     setUserConfig: (data: API.UserInfo | null) => void;
 }
 
+function getInitialUserConfig(): UserConfigState {
+    const stored = ls.getLocal<API.UserInfo>(userKey);
+    if (!stored) return { userConfig: null };
+    const notExpired = (stored.accessExpires ?? 0) - currentUnixTimestamp() > 0;
+    if (!notExpired) return { userConfig: null };
+    return { userConfig: stored };
+}
+
 const useUserConfigModel = (): UserConfigModel => {
-    const [state, setState] = useState<UserConfigState>({
-        userConfig: null,
-    });
+    const [state, setState] = useState<UserConfigState>(getInitialUserConfig);
 
     const setUserConfig = useCallback((data: API.UserInfo | null) => {
         setState({ userConfig: data });

+ 12 - 5
src/pages/pricing/components/UserInfo/index.tsx

@@ -3,21 +3,28 @@ import { memo } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import { useResponsive } from '@/hooks/useResponsive';
+import { maskAccount } from '@/utils/stringUtils';
+import { unixTimeFormat } from '@/utils/timeUtils';
+
 import LabelValueItem from '../LabelValueItem';
 import { useService } from './useService';
-import { unixTimeFormat } from '@/utils/timeUtils';
 
 const UserInfo = memo(() => {
     const { t } = useTranslation();
     const { isMobile } = useResponsive();
-    const { userAccount, planExpireDate } = useService();
+    const { userAccount, planExpireTime } = useService();
+
+    if (!userAccount) return null;
 
     return (
         <div className={`${isMobile ? 'flex flex-col gap-2' : 'flex-row-bc'}`}>
-            <LabelValueItem label={t('pages.pricing.userInfo.account')} value={userAccount} />
             <LabelValueItem
-                label={t('pages.pricing.userInfo.planExpireDate')}
-                value={planExpireDate > 0 ? unixTimeFormat(planExpireDate) : '-'}
+                label={t('pages.pricing.userInfo.account')}
+                value={maskAccount(userAccount)}
+            />
+            <LabelValueItem
+                label={t('pages.pricing.userInfo.planExpireTime')}
+                value={planExpireTime > 0 ? unixTimeFormat(planExpireTime, 'YYYY-MM-DD') : '-'}
             />
         </div>
     );

+ 4 - 6
src/pages/pricing/components/UserInfo/useService.ts

@@ -4,7 +4,7 @@ import { userConfigModel } from '@/models/userConfigModel';
 
 export interface UseServiceReturn {
     userAccount: string;
-    planExpireDate: number;
+    planExpireTime: number;
 }
 
 export function useService(): UseServiceReturn {
@@ -15,14 +15,12 @@ export function useService(): UseServiceReturn {
         return userConfig.account?.username || '';
     }, [userConfig]);
 
-    const planExpireDate = useMemo(() => {
-        if (!userConfig) return 0;
-        // TODO: 调用 api 获取用户当前套餐信息,返回套餐到期时间
-        return 0;
+    const planExpireTime = useMemo(() => {
+        return userConfig?.expireTime ?? 0;
     }, [userConfig]);
 
     return {
         userAccount,
-        planExpireDate,
+        planExpireTime,
     };
 }

+ 4 - 0
src/pages/redirect/index.tsx

@@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
 
 import { removeToken, setToken } from '@/utils/authUtils';
 import { decryptUrlParams } from '@/utils/requestCrypto';
+import { userConfigModel } from '@/models/userConfigModel';
 
 /**
  * 解密重定向参数
@@ -22,6 +23,8 @@ const Redirect: React.FC = () => {
     const navigate = useNavigate();
     const [searchParams] = useSearchParams();
 
+    const { setUserConfig } = userConfigModel.useModel();
+
     useEffect(() => {
         const redirectParam = searchParams.get('d');
         console.log('🚀 ~ Redirect ~ redirectParam:', redirectParam);
@@ -50,6 +53,7 @@ const Redirect: React.FC = () => {
                 if (accessToken) {
                     removeToken(); // 删除旧的 token
                     setToken({ accessToken, accessExpires: expireTime }); // 设置新的 token
+                    setUserConfig(null); // 清除用户信息
                 }
 
                 // 跳转到指定路由

+ 12 - 0
src/utils/stringUtils.ts

@@ -61,6 +61,18 @@ export function isEmail(str: string): boolean {
     return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(str);
 }
 
+/**
+ * 账号脱敏:中间用 ** 代替,前后各保留 4 位
+ * @param str 原始账号
+ * @returns 脱敏后的字符串,如 user**com.
+ */
+export function maskAccount(str: string): string {
+    if (!str) return '';
+    const len = str.length;
+    if (len <= 8) return '**xxxx';
+    return str.slice(0, 4) + '**' + str.slice(-4);
+}
+
 /**
  * 生成具备加密强度的唯一ID,该算法能保证在同一时刻生成的ID不会重复,并具有加密强度(不可预测),但效率稍低(需调用底层的操作系统加密API,实测100000次调用,需450毫秒左右)
  * @returns