优化打手订单结算和大厅加载
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
SearchOutlined,
|
||||
TrophyOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Pagination,
|
||||
Space,
|
||||
Spin,
|
||||
Switch,
|
||||
@@ -26,7 +25,7 @@ import {
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import {
|
||||
fetchWorkerHallOrders,
|
||||
@@ -44,6 +43,8 @@ type HallSummary = {
|
||||
problemCount: number
|
||||
}
|
||||
|
||||
const HALL_PAGE_SIZE = 20
|
||||
|
||||
export default function WorkerHallPage() {
|
||||
const isMobile = useIsMobile()
|
||||
const { message } = App.useApp()
|
||||
@@ -51,14 +52,13 @@ export default function WorkerHallPage() {
|
||||
const [keywordInput, setKeywordInput] = useState('')
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [categoryId, setCategoryId] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [grabbingId, setGrabbingId] = useState(0)
|
||||
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [leaderboardOpen, setLeaderboardOpen] = useState(false)
|
||||
const [sharingQuantity, setSharingQuantity] = useState(1)
|
||||
const [submittingSharing, setSubmittingSharing] = useState(false)
|
||||
const [autoRefresh, setAutoRefresh] = useState(loadAutoRefreshPreference)
|
||||
const loadMoreRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoRefresh) return
|
||||
@@ -73,10 +73,22 @@ export default function WorkerHallPage() {
|
||||
localStorage.setItem(AUTO_REFRESH_STORAGE_KEY, enabled ? '1' : '0')
|
||||
}
|
||||
|
||||
const ordersQuery = useQuery({
|
||||
queryKey: ['worker-hall-orders', keyword, categoryId, page, pageSize],
|
||||
queryFn: () =>
|
||||
fetchWorkerHallOrders({ keyword, page, pageSize, categoryId: categoryId || undefined }),
|
||||
const ordersQuery = useInfiniteQuery({
|
||||
queryKey: ['worker-hall-orders', keyword, categoryId],
|
||||
initialPageParam: 1,
|
||||
queryFn: ({ pageParam }) =>
|
||||
fetchWorkerHallOrders({
|
||||
keyword,
|
||||
page: Number(pageParam),
|
||||
pageSize: HALL_PAGE_SIZE,
|
||||
categoryId: categoryId || undefined,
|
||||
}),
|
||||
getNextPageParam: (lastPage) => {
|
||||
const pagination = lastPage.data.pagination
|
||||
return pagination.page * pagination.pageSize < pagination.total
|
||||
? pagination.page + 1
|
||||
: undefined
|
||||
},
|
||||
retry: false,
|
||||
})
|
||||
|
||||
@@ -121,9 +133,10 @@ export default function WorkerHallPage() {
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const orders = ordersQuery.data?.data.items || []
|
||||
const pagination = ordersQuery.data?.data.pagination
|
||||
const categories = ordersQuery.data?.data.categories || []
|
||||
const orders = ordersQuery.data?.pages.flatMap((page) => page.data.items) || []
|
||||
const firstPage = ordersQuery.data?.pages[0]?.data
|
||||
const categories = firstPage?.categories || []
|
||||
const totalOrderCount = Number(firstPage?.pagination.total || 0)
|
||||
const worker = profileQuery.data?.data.worker
|
||||
const activeCount = Number(profileQuery.data?.data.summary.activeOrderCount || 0)
|
||||
const pendingAcceptanceCount = hallSummaryQuery.data?.pendingAcceptanceCount || 0
|
||||
@@ -132,9 +145,36 @@ export default function WorkerHallPage() {
|
||||
const remainingSlots = maxActiveOrders > 0 ? Math.max(0, maxActiveOrders - activeCount) : null
|
||||
const shouldWarn = (remainingSlots !== null && remainingSlots <= 0) || problemCount > 0
|
||||
|
||||
useEffect(() => {
|
||||
const target = loadMoreRef.current
|
||||
if (
|
||||
!target ||
|
||||
!ordersQuery.hasNextPage ||
|
||||
ordersQuery.isFetchingNextPage ||
|
||||
ordersQuery.isFetchNextPageError
|
||||
) {
|
||||
return
|
||||
}
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0]?.isIntersecting) {
|
||||
void ordersQuery.fetchNextPage()
|
||||
}
|
||||
},
|
||||
{ rootMargin: '320px 0px' },
|
||||
)
|
||||
observer.observe(target)
|
||||
return () => observer.disconnect()
|
||||
}, [
|
||||
ordersQuery.fetchNextPage,
|
||||
ordersQuery.hasNextPage,
|
||||
ordersQuery.isFetchNextPageError,
|
||||
ordersQuery.isFetchingNextPage,
|
||||
])
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-hall-orders'] }),
|
||||
queryClient.resetQueries({ queryKey: ['worker-hall-orders'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-hall-summary'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-profile'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }),
|
||||
@@ -195,9 +235,20 @@ export default function WorkerHallPage() {
|
||||
|
||||
function applyKeywordSearch() {
|
||||
setKeyword(keywordInput.trim())
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const loadMoreIndicator =
|
||||
orders.length > 0 ? (
|
||||
<div ref={loadMoreRef} className="worker-hall-load-more" aria-live="polite">
|
||||
{ordersQuery.isFetchingNextPage ? <Spin size="small" /> : null}
|
||||
{ordersQuery.isFetchingNextPage ? <span>加载中</span> : null}
|
||||
{ordersQuery.isFetchNextPageError ? <span>加载失败,请刷新后重试</span> : null}
|
||||
{!ordersQuery.hasNextPage && !ordersQuery.isFetchingNextPage ? (
|
||||
<span>已加载全部 {totalOrderCount} 条</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null
|
||||
|
||||
const summaryItems = [
|
||||
{
|
||||
label: '当前余额',
|
||||
@@ -272,7 +323,6 @@ export default function WorkerHallPage() {
|
||||
className={`worker-hall-mobile-cat-pill ${categoryId === 0 ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setCategoryId(0)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
全部
|
||||
@@ -284,7 +334,6 @@ export default function WorkerHallPage() {
|
||||
className={`worker-hall-mobile-cat-pill ${categoryId === category.categoryId ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setCategoryId(category.categoryId)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
{category.name}
|
||||
@@ -306,7 +355,6 @@ export default function WorkerHallPage() {
|
||||
setKeywordInput(nextKeyword)
|
||||
if (!nextKeyword.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
@@ -340,7 +388,7 @@ export default function WorkerHallPage() {
|
||||
<div className="worker-hall-loading">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : ordersQuery.error ? (
|
||||
) : ordersQuery.error && orders.length === 0 ? (
|
||||
<Empty
|
||||
description={
|
||||
ordersQuery.error instanceof Error
|
||||
@@ -455,20 +503,7 @@ export default function WorkerHallPage() {
|
||||
})
|
||||
)}
|
||||
|
||||
{(pagination?.total || 0) > 0 ? (
|
||||
<div className="worker-orders-mobile-pagination">
|
||||
<Pagination
|
||||
current={pagination?.page || page}
|
||||
pageSize={pagination?.pageSize || pageSize}
|
||||
total={pagination?.total || 0}
|
||||
simple
|
||||
onChange={(nextPage: number, nextPageSize: number) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{loadMoreIndicator}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -528,7 +563,6 @@ export default function WorkerHallPage() {
|
||||
setKeywordInput(nextKeyword)
|
||||
if (!nextKeyword.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
@@ -569,7 +603,6 @@ export default function WorkerHallPage() {
|
||||
]}
|
||||
onChange={(nextKey) => {
|
||||
setCategoryId(Number(nextKey) || 0)
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
@@ -591,7 +624,7 @@ export default function WorkerHallPage() {
|
||||
<div className="worker-hall-loading">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : ordersQuery.error ? (
|
||||
) : ordersQuery.error && orders.length === 0 ? (
|
||||
<Empty
|
||||
description={
|
||||
ordersQuery.error instanceof Error
|
||||
@@ -704,23 +737,7 @@ export default function WorkerHallPage() {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{(pagination?.total || 0) > 0 ? (
|
||||
<div className="worker-hall-pagination">
|
||||
<Pagination
|
||||
current={pagination?.page || page}
|
||||
pageSize={pagination?.pageSize || pageSize}
|
||||
total={pagination?.total || 0}
|
||||
showSizeChanger={!isMobile}
|
||||
simple={isMobile}
|
||||
pageSizeOptions={[20, 50, 100]}
|
||||
showTotal={isMobile ? undefined : (total) => `共 ${total} 条`}
|
||||
onChange={(nextPage, nextPageSize) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{loadMoreIndicator}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -34,6 +34,7 @@ import WorkOrderEventTimeline from '@/components/WorkOrderEventTimeline'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import ImageUpload from '@/components/files/ImageUpload'
|
||||
import {
|
||||
fetchWorkerMyOrderOverview,
|
||||
fetchWorkerMyOrders,
|
||||
fetchWorkerProfile,
|
||||
saveWorkerAcceptanceDraft,
|
||||
@@ -215,17 +216,12 @@ export default function WorkerOrdersPage() {
|
||||
|
||||
const overviewQuery = useQuery({
|
||||
queryKey: ['worker-my-orders-overview'],
|
||||
queryFn: async () => {
|
||||
const results = await Promise.all(
|
||||
STATUS_OPTIONS.map(async (item) => {
|
||||
const response = await fetchWorkerMyOrders(buildWorkerOrderQuery(item.value, '', 1, 1))
|
||||
return [item.value || 'all', response.data.pagination.total] as const
|
||||
}),
|
||||
)
|
||||
return Object.fromEntries(results) as Record<string, number>
|
||||
},
|
||||
queryFn: fetchWorkerMyOrderOverview,
|
||||
retry: false,
|
||||
})
|
||||
const overviewByStatus = new Map(
|
||||
(overviewQuery.data?.data.items || []).map((item) => [item.status, item]),
|
||||
)
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([
|
||||
@@ -615,8 +611,11 @@ export default function WorkerOrdersPage() {
|
||||
<div className="worker-orders-mobile-status-bar">
|
||||
{STATUS_OPTIONS.map((item) => {
|
||||
const countKey = item.value || 'all'
|
||||
const count = Number(overviewQuery.data?.[countKey] || 0)
|
||||
const overview = overviewByStatus.get(countKey)
|
||||
const count = Number(overview?.orderCount || 0)
|
||||
const settlementAmount = Number(overview?.settlementAmount || 0)
|
||||
const isActive = (status || '') === item.value
|
||||
const settlementLabel = getSettlementLabel(item.value)
|
||||
return (
|
||||
<button
|
||||
key={item.value || 'all'}
|
||||
@@ -627,10 +626,15 @@ export default function WorkerOrdersPage() {
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
{count > 0 ? (
|
||||
<span className="worker-orders-status-pill-count">{count}</span>
|
||||
) : null}
|
||||
<span className="worker-orders-status-pill-main">
|
||||
<span>{item.label}</span>
|
||||
{count > 0 ? (
|
||||
<span className="worker-orders-status-pill-count">{count}</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="worker-orders-status-pill-settlement">
|
||||
{settlementLabel} {formatMoney(settlementAmount)}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
@@ -928,16 +932,26 @@ export default function WorkerOrdersPage() {
|
||||
<Card bordered={false}>
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
<Tabs
|
||||
className="worker-order-tabs"
|
||||
activeKey={status || 'all'}
|
||||
items={STATUS_OPTIONS.map((item) => {
|
||||
const countKey = item.value || 'all'
|
||||
const count = Number(overviewQuery.data?.[countKey] || 0)
|
||||
const overview = overviewByStatus.get(countKey)
|
||||
const count = Number(overview?.orderCount || 0)
|
||||
const settlementAmount = Number(overview?.settlementAmount || 0)
|
||||
return {
|
||||
key: item.value || 'all',
|
||||
label: (
|
||||
<span>
|
||||
{item.label}
|
||||
{count > 0 ? <span className="worker-order-tab-count">{count}</span> : null}
|
||||
<span className="worker-order-tab-label">
|
||||
<span className="worker-order-tab-label-main">
|
||||
{item.label}
|
||||
{count > 0 ? (
|
||||
<span className="worker-order-tab-count">{count}</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="worker-order-tab-settlement">
|
||||
{getSettlementLabel(item.value)} {formatMoney(settlementAmount)}
|
||||
</span>
|
||||
</span>
|
||||
),
|
||||
}
|
||||
@@ -1519,6 +1533,12 @@ function formatMoney(value: number | undefined) {
|
||||
return `¥${((Number(value || 0) || 0) / 100).toFixed(2)}`
|
||||
}
|
||||
|
||||
function getSettlementLabel(status: string) {
|
||||
if (status === 'accepted' || status === 'vip_evidence_pending') return '已结算'
|
||||
if (!status) return '结算合计'
|
||||
return '预计'
|
||||
}
|
||||
|
||||
function formatStatus(status: string) {
|
||||
const labels: Record<string, string> = {
|
||||
open: '已超时',
|
||||
|
||||
Reference in New Issue
Block a user