fix(worker-hall): 优化抢单大厅两级分类联动体验并支持分类图片浏览器长效缓存

This commit is contained in:
yml2213
2026-09-13 11:02:10 +08:00
parent 9fcc4ef99d
commit 85272cde46
10 changed files with 169 additions and 11 deletions
@@ -12,7 +12,7 @@ import {
TrophyOutlined,
WalletOutlined,
} from '@ant-design/icons'
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
import { keepPreviousData, useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
import {
Alert,
App,
@@ -36,6 +36,7 @@ import { useEffect, useRef, useState } from 'react'
import { Link } from 'react-router'
import {
fetchWorkerHallCategories,
fetchWorkerHallOrders,
fetchWorkerHallLeaderboard,
fetchWorkerMyOrders,
@@ -44,7 +45,12 @@ import {
grabWorkerOrder,
joinWorkerSharingOrder,
} from '@/services/worker'
import type { WorkOrder, WorkerLeaderboardPeriod, WorkerUser } from '@/types/worker-platform'
import type {
WorkCategory,
WorkOrder,
WorkerLeaderboardPeriod,
WorkerUser,
} from '@/types/worker-platform'
import { useIsMobile } from '@/utils/use-is-mobile'
import {
isGrabConfirmEnabled,
@@ -84,6 +90,15 @@ export default function WorkerHallPage() {
const [grabConfirmEnabled, setGrabConfirmEnabled] = useState(isGrabConfirmEnabled)
const loadMoreRef = useRef<HTMLDivElement | null>(null)
const categoriesQuery = useQuery({
queryKey: ['worker-hall-categories'],
queryFn: () => fetchWorkerHallCategories(),
staleTime: 5 * 60_000,
gcTime: 30 * 60_000,
refetchOnWindowFocus: false,
retry: false,
})
const ordersQuery = useInfiniteQuery({
queryKey: ['worker-hall-orders', keyword, effectiveCategoryId],
initialPageParam: 1,
@@ -100,6 +115,7 @@ export default function WorkerHallPage() {
? pagination.page + 1
: undefined
},
placeholderData: keepPreviousData,
staleTime: 2_000,
refetchOnWindowFocus: false,
retry: false,
@@ -138,13 +154,51 @@ export default function WorkerHallPage() {
const orders = ordersQuery.data?.pages.flatMap((page) => page.data.items) || []
const firstPage = ordersQuery.data?.pages[0]?.data
const allCategories = firstPage?.categories || []
const showSubcategoriesConfig = firstPage?.showSubcategories ?? true
const [categoryMeta, setCategoryMeta] = useState<{
categories: WorkCategory[]
showSubcategories: boolean
allIconUrl: string | null
}>({
categories: [],
showSubcategories: true,
allIconUrl: null,
})
useEffect(() => {
const fromApi = categoriesQuery.data?.data
if (fromApi && fromApi.categories && fromApi.categories.length > 0) {
setCategoryMeta({
categories: fromApi.categories,
showSubcategories: fromApi.showSubcategories ?? true,
allIconUrl: fromApi.allIconUrl || null,
})
return
}
if (firstPage && firstPage.categories && firstPage.categories.length > 0) {
setCategoryMeta({
categories: firstPage.categories,
showSubcategories: firstPage.showSubcategories ?? true,
allIconUrl: firstPage.allIconUrl || null,
})
}
}, [categoriesQuery.data, firstPage])
const allCategories =
categoryMeta.categories.length > 0
? categoryMeta.categories
: categoriesQuery.data?.data?.categories || firstPage?.categories || []
const showSubcategoriesConfig = categoryMeta.showSubcategories
const allCategoryFromList = allCategories.find(
(c) => !c.parentId && (c.categoryKey === 'all' || c.name === '全部'),
)
const allIconUrl = firstPage?.allIconUrl || allCategoryFromList?.iconUrl || null
const allIconUrl =
categoryMeta.allIconUrl ||
categoriesQuery.data?.data?.allIconUrl ||
firstPage?.allIconUrl ||
allCategoryFromList?.iconUrl ||
null
const primaryCategories = allCategories.filter(
(c) => !c.parentId && c.categoryKey !== 'all' && c.name !== '全部',
@@ -168,6 +222,10 @@ export default function WorkerHallPage() {
setPrimaryCategoryId(nextId)
setSubCategoryId(0)
}
useEffect(() => {
setFailedImageKeys({})
}, [allCategories.length, allIconUrl])
const worker = profileQuery.data?.data.worker
const activeCount = Number(profileQuery.data?.data.summary.activeOrderCount || 0)
const pendingAcceptanceCount = hallSummaryQuery.data?.pendingAcceptanceCount || 0
@@ -208,7 +266,8 @@ export default function WorkerHallPage() {
async function refreshAll() {
await Promise.all([
queryClient.resetQueries({ queryKey: ['worker-hall-orders'] }),
ordersQuery.refetch(),
queryClient.invalidateQueries({ queryKey: ['worker-hall-categories'] }),
queryClient.invalidateQueries({ queryKey: ['worker-hall-summary'] }),
queryClient.invalidateQueries({ queryKey: ['worker-profile'] }),
queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }),
@@ -395,6 +454,8 @@ export default function WorkerHallPage() {
src={allIconUrl}
alt="全部"
className="worker-hall-game-img"
loading="lazy"
decoding="async"
onError={() => setFailedImageKeys((prev) => ({ ...prev, all: true }))}
/>
) : (
@@ -428,6 +489,8 @@ export default function WorkerHallPage() {
src={category.iconUrl}
alt={category.name}
className="worker-hall-game-img"
loading="lazy"
decoding="async"
onError={() =>
setFailedImageKeys((prev) => ({
...prev,
@@ -839,6 +902,8 @@ export default function WorkerHallPage() {
src={allIconUrl}
alt="全部"
className="worker-hall-game-img"
loading="lazy"
decoding="async"
onError={() => setFailedImageKeys((prev) => ({ ...prev, all: true }))}
/>
) : (
@@ -874,6 +939,8 @@ export default function WorkerHallPage() {
src={category.iconUrl}
alt={category.name}
className="worker-hall-game-img"
loading="lazy"
decoding="async"
onError={() =>
setFailedImageKeys((prev) => ({
...prev,
+5
View File
@@ -11,6 +11,7 @@ import type {
WorkerAnnouncementConfig,
WorkerOrderFeedback,
WorkerAfterSalesCase,
WorkerHallCategoriesResponse,
WorkerHallOrdersResponse,
WorkerLeaderboardResponse,
WorkerLeaderboardPeriod,
@@ -205,6 +206,10 @@ export function renameWorkerAccount(payload: { displayName: string }) {
)
}
export function fetchWorkerHallCategories() {
return apiGet<WorkerHallCategoriesResponse>('/api/v1/worker/hall/categories')
}
export function fetchWorkerHallOrders(params?: Record<string, unknown>) {
return apiGet<WorkerHallOrdersResponse>('/api/v1/worker/hall/orders', params)
}
@@ -723,6 +723,12 @@ export type WorkerHallOrdersResponse = {
visibleDelaySeconds?: number
}
export type WorkerHallCategoriesResponse = {
categories: WorkCategory[]
showSubcategories?: boolean
allIconUrl?: string | null
}
export type WorkerLeaderboardEntry = {
rank: number
workerId: number