fix(lang): 优化语言处理和数据加载逻辑

This commit is contained in:
JiaJun
2026-04-15 10:32:02 +08:00
parent e9d0d46244
commit bc87ef721a
6 changed files with 69 additions and 26 deletions

View File

@@ -30,7 +30,10 @@ function App() {
if (previousLanguage && previousLanguage !== normalizedLanguage) { if (previousLanguage && previousLanguage !== normalizedLanguage) {
await queryClient.cancelQueries() await queryClient.cancelQueries()
await queryClient.invalidateQueries({refetchType: 'active'}) await queryClient.invalidateQueries({
refetchType: 'active',
predicate: (query) => query.queryKey[0] !== 'auth-bootstrap',
})
} }
previousLanguageRef.current = normalizedLanguage previousLanguageRef.current = normalizedLanguage
@@ -120,7 +123,7 @@ function App() {
</div> </div>
} }
> >
<Routes> <Routes>
<Route path="/" element={<HomePage/>}/> <Route path="/" element={<HomePage/>}/>
<Route path="/goods" element={<GoodsPage/>}/> <Route path="/goods" element={<GoodsPage/>}/>
<Route path="/record" element={<RecordPage/>}/> <Route path="/record" element={<RecordPage/>}/>

View File

@@ -48,6 +48,7 @@ export function useAddressBook(options?: UseAddressBookOptions) {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const sessionId = useUserStore((state) => state.authInfo?.session_id ?? '') const sessionId = useUserStore((state) => state.authInfo?.session_id ?? '')
const [addressForm, setAddressForm] = useState<AddAddressForm>(emptyAddressForm) const [addressForm, setAddressForm] = useState<AddAddressForm>(emptyAddressForm)
const [loadAddressesLoading, setLoadAddressesLoading] = useState(false)
const addressListQuery = useQuery({ const addressListQuery = useQuery({
queryKey: queryKeys.addressList(sessionId), queryKey: queryKeys.addressList(sessionId),
@@ -100,6 +101,8 @@ export function useAddressBook(options?: UseAddressBookOptions) {
return [] return []
} }
setLoadAddressesLoading(true)
try { try {
const result = await queryClient.fetchQuery({ const result = await queryClient.fetchQuery({
queryKey: queryKeys.addressList(sessionId), queryKey: queryKeys.addressList(sessionId),
@@ -112,6 +115,8 @@ export function useAddressBook(options?: UseAddressBookOptions) {
return result return result
} catch { } catch {
return [] return []
} finally {
setLoadAddressesLoading(false)
} }
}, [queryClient, sessionId]) }, [queryClient, sessionId])
@@ -177,7 +182,7 @@ export function useAddressBook(options?: UseAddressBookOptions) {
sessionId, sessionId,
addresses, addresses,
addressOptions, addressOptions,
loading: addressListQuery.isPending, loading: addressListQuery.isPending || loadAddressesLoading,
addressForm, addressForm,
isAddressFormValid, isAddressFormValid,
submitLoading: saveAddressMutation.isPending, submitLoading: saveAddressMutation.isPending,

View File

@@ -4,6 +4,7 @@ import {type PropsWithChildren, useEffect, useRef, useState} from 'react'
import {login, validateToken} from '@/api/auth.ts' import {login, validateToken} from '@/api/auth.ts'
import {userAssets} from '@/api/user.ts' import {userAssets} from '@/api/user.ts'
import {useTranslation} from 'react-i18next' import {useTranslation} from 'react-i18next'
import {normalizeLanguage} from '@/lib/i18n'
import {queryKeys} from '@/lib/queryKeys.ts' import {queryKeys} from '@/lib/queryKeys.ts'
import {useUserStore} from '@/store/user.ts' import {useUserStore} from '@/store/user.ts'
import type {HostContextMessage} from '@/types' import type {HostContextMessage} from '@/types'
@@ -70,7 +71,7 @@ export function AuthGuide({children}: PropsWithChildren) {
} }
if (typeof language === 'string' && language.trim()) { if (typeof language === 'string' && language.trim()) {
setLanguage(language.trim()) setLanguage(normalizeLanguage(language))
} }
} }

View File

@@ -4,7 +4,7 @@ import {useTranslation} from 'react-i18next'
import {ChevronRight} from 'lucide-react' import {ChevronRight} from 'lucide-react'
import Button from '@/components/button' import Button from '@/components/button'
import {HOME_CATEGORY_META_MAP} from '@/constant' import {HOME_CATEGORY_META_MAP, HOME_GOOD_TYPE_ORDER} from '@/constant'
import type {ProductCategory, ProductItem} from '@/types' import type {ProductCategory, ProductItem} from '@/types'
type GoodsCategoryListProps = { type GoodsCategoryListProps = {
@@ -56,12 +56,52 @@ export function GoodsCategoryList({
}: GoodsCategoryListProps) { }: GoodsCategoryListProps) {
const {t} = useTranslation() const {t} = useTranslation()
const resolvedEmptyText = emptyText ?? t('goods.noGoodsAvailableYet') const resolvedEmptyText = emptyText ?? t('goods.noGoodsAvailableYet')
if (loading) { if (loading) {
return ( return (
<div className="pb-[24px] mt-[20px]"> <div className="pb-[24px]">
<div className="liquid-glass-bg px-[16px] py-[18px] text-[14px] text-white/60"> {HOME_GOOD_TYPE_ORDER.map((categoryId) => {
{t('goods.loading')} const CategoryIcon = HOME_CATEGORY_META_MAP[categoryId].icon
</div>
return (
<div key={categoryId} className="mt-[20px]">
<div className="mb-[10px] flex items-center justify-between gap-[12px]">
<div className="flex min-w-0 items-center gap-[10px]">
<div
className="flex h-[36px] w-[36px] items-center justify-center rounded-[12px] bg-[#FA6A00]/15 text-[#FE9F00]">
<CategoryIcon className="h-[18px] w-[18px]" aria-hidden="true"/>
</div>
<div className="truncate text-[15px] font-semibold text-white">
{t(HOME_CATEGORY_META_MAP[categoryId].nameKey)}
</div>
</div>
{showMore ? (
<div className="text-[12px] font-light text-[#FA6A00] underline">
{t('common.more')}
</div>
) : null}
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
{Array.from({length: 4}).map((_, index) => (
<div
key={`${categoryId}-${index}`}
className="liquid-glass-bg flex min-h-[260px] w-full flex-col items-stretch justify-start overflow-hidden"
>
<div className="h-[116px] w-full bg-white/6 sm:h-[128px]"></div>
<div className="flex flex-1 flex-col items-start justify-between gap-[12px] p-[12px] sm:p-[14px]">
<div className="w-full space-y-[8px]">
<div className="h-[18px] w-[72%] rounded-full bg-white/10"></div>
<div className="h-[14px] w-[48%] rounded-full bg-white/8"></div>
</div>
<div className="h-[18px] w-[92px] rounded-full bg-[#FA6A00]/18"></div>
<div className="h-[36px] w-full rounded-[10px] bg-[#FA6A00]/20"></div>
</div>
</div>
))}
</div>
</div>
)
})}
</div> </div>
) )
} }

View File

@@ -2,6 +2,7 @@ import ky, {HTTPError, type AfterResponseHook, type Input, type KyInstance, type
import {notifyError, resolveToastMessage} from '@/features/notifications' import {notifyError, resolveToastMessage} from '@/features/notifications'
import i18n from '@/lib/i18n' import i18n from '@/lib/i18n'
import {normalizeLanguage} from '@/lib/i18n'
import {useUserStore} from '@/store/user.ts' import {useUserStore} from '@/store/user.ts'
import type {ValidateTokenData} from '@/types/auth.type.ts' import type {ValidateTokenData} from '@/types/auth.type.ts'
import {objectToFormData} from './tool' import {objectToFormData} from './tool'
@@ -67,7 +68,7 @@ export const setAccessTokenFormatter = (formatter?: TokenFormatter) => {
accessTokenFormatter = formatter ?? ((token) => `Bearer ${token}`) accessTokenFormatter = formatter ?? ((token) => `Bearer ${token}`)
} }
const getRequestLanguage = () => useUserStore.getState().language?.trim() || 'zh' const getRequestLanguage = () => normalizeLanguage(useUserStore.getState().language)
const authRefreshClient = ky.create({ const authRefreshClient = ky.create({
baseUrl: API_BASE_URL, baseUrl: API_BASE_URL,

View File

@@ -103,20 +103,6 @@ function getOrderStatus(status?: string | number) {
} }
function getOrderCategory(item: OrderItem) { function getOrderCategory(item: OrderItem) {
if (item.type?.trim()) {
const normalizedType = item.type.trim().toUpperCase()
switch (normalizedType) {
case 'BONUS':
return i18n.t('record.categories.bonus')
case 'PHYSICAL':
return i18n.t('record.categories.physical')
case 'WITHDRAW':
return i18n.t('record.categories.withdraw')
default:
return toTitleCase(normalizedType)
}
}
if (item.type_title) { if (item.type_title) {
return item.type_title return item.type_title
} }
@@ -126,15 +112,22 @@ function getOrderCategory(item: OrderItem) {
} }
if (item.type) { if (item.type) {
switch (item.type) { const normalizedType = item.type.trim().toUpperCase()
switch (normalizedType) {
case 'BONUS': case 'BONUS':
return i18n.t('record.categories.bonus') return i18n.t('record.categories.bonus')
case 'PHYSICAL': case 'PHYSICAL':
return i18n.t('record.categories.physical') return i18n.t('record.categories.physical')
case 'WITHDRAW': case 'WITHDRAW':
return i18n.t('record.categories.withdraw') return i18n.t('record.categories.withdraw')
case '1':
return i18n.t('record.categories.withdraw')
case '2':
return i18n.t('record.categories.physical')
case '3':
return i18n.t('record.categories.bonus')
default: default:
return toTitleCase(item.type) return toTitleCase(normalizedType)
} }
} }