优化打手订单结算和大厅加载
This commit is contained in:
@@ -48,6 +48,14 @@ const WORK_ORDER_SHARE_SELECT = `
|
||||
LEFT JOIN worker_users wu ON wu.id = wos.worker_id
|
||||
`
|
||||
|
||||
type WorkerOrderOverviewRow = {
|
||||
status: string
|
||||
order_count: number
|
||||
settlement_amount: number
|
||||
vip_evidence_pending_count: number
|
||||
vip_evidence_pending_settlement_amount: number
|
||||
}
|
||||
|
||||
export async function listWorkOrderShares(
|
||||
workOrderId: number | string,
|
||||
): Promise<WorkOrderShareRow[]> {
|
||||
@@ -820,6 +828,81 @@ export async function listWorkOrders({
|
||||
return { items: itemsResult.rows, total: Number(totalResult.rows[0]?.total || 0) }
|
||||
}
|
||||
|
||||
/** 汇总打手本人各订单状态的可结算金额,拼单按个人份额计算。 */
|
||||
export async function getWorkerOrderOverview(workerId: number | string): Promise<
|
||||
Array<{
|
||||
status: string
|
||||
orderCount: number
|
||||
settlementAmount: number
|
||||
vipEvidencePendingCount: number
|
||||
vipEvidencePendingSettlementAmount: number
|
||||
}>
|
||||
> {
|
||||
const normalizedWorkerId = Number(workerId)
|
||||
const result = await query<WorkerOrderOverviewRow>(
|
||||
`
|
||||
WITH worker_orders AS (
|
||||
SELECT
|
||||
wo.status,
|
||||
wo.acceptance_json,
|
||||
wos.id IS NOT NULL AS is_worker_share,
|
||||
CASE
|
||||
WHEN wos.id IS NOT NULL THEN wos.share_reward
|
||||
WHEN wo.assigned_worker_id = $1 THEN wo.reward_amount
|
||||
ELSE 0
|
||||
END AS worker_settlement_amount
|
||||
FROM work_orders wo
|
||||
LEFT JOIN work_order_shares wos
|
||||
ON wos.work_order_id = wo.id
|
||||
AND wos.worker_id = $1
|
||||
AND wos.status != 'cancelled'
|
||||
WHERE wo.assigned_worker_id = $1
|
||||
OR wo.last_assigned_worker_id = $1
|
||||
OR wos.id IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
status,
|
||||
COUNT(*)::int AS order_count,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN status IN ('in_progress', 'pending_acceptance', 'problem', 'accepted')
|
||||
OR (status = 'open' AND is_worker_share)
|
||||
THEN worker_settlement_amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
)::int AS settlement_amount,
|
||||
COUNT(*) FILTER (
|
||||
WHERE status = 'accepted'
|
||||
AND acceptance_json->>'reviewMode' = 'vip_auto'
|
||||
AND acceptance_json->>'evidenceStatus' = 'pending'
|
||||
AND NULLIF(acceptance_json->>'evidenceDueAt', '')::timestamptz > NOW()
|
||||
)::int AS vip_evidence_pending_count,
|
||||
COALESCE(
|
||||
SUM(worker_settlement_amount) FILTER (
|
||||
WHERE status = 'accepted'
|
||||
AND acceptance_json->>'reviewMode' = 'vip_auto'
|
||||
AND acceptance_json->>'evidenceStatus' = 'pending'
|
||||
AND NULLIF(acceptance_json->>'evidenceDueAt', '')::timestamptz > NOW()
|
||||
),
|
||||
0
|
||||
)::int AS vip_evidence_pending_settlement_amount
|
||||
FROM worker_orders
|
||||
GROUP BY status
|
||||
`,
|
||||
[normalizedWorkerId],
|
||||
)
|
||||
return result.rows.map((row) => ({
|
||||
status: row.status,
|
||||
orderCount: Number(row.order_count || 0),
|
||||
settlementAmount: Number(row.settlement_amount || 0),
|
||||
vipEvidencePendingCount: Number(row.vip_evidence_pending_count || 0),
|
||||
vipEvidencePendingSettlementAmount: Number(row.vip_evidence_pending_settlement_amount || 0),
|
||||
}))
|
||||
}
|
||||
|
||||
export async function updateWorkOrder(
|
||||
workOrderId: number | string,
|
||||
patch: Partial<
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getWorkerProfile,
|
||||
getWorkerSessionSummary,
|
||||
getWorkerHallLeaderboard,
|
||||
getWorkerMyOrderOverview,
|
||||
grabWorkerHallOrder,
|
||||
joinWorkerSharingOrder,
|
||||
listWorkerProfileFinanceRequests,
|
||||
@@ -268,6 +269,16 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/orders/overview',
|
||||
requireActiveWorker,
|
||||
createRouteHandler((req) => getWorkerMyOrderOverview(getRequiredWorkerSession(req)), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取我的订单概览失败',
|
||||
scope: '[worker/orders/overview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/orders/:workOrderId/events',
|
||||
requireActiveWorker,
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
getWorkerUserByDisplayName,
|
||||
getWorkOrderById,
|
||||
getWorkOrderShare,
|
||||
getWorkerOrderOverview,
|
||||
grabWorkOrder,
|
||||
incrementWorkerSessionVersion,
|
||||
joinWorkOrderShare,
|
||||
@@ -1148,6 +1149,40 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker
|
||||
}
|
||||
}
|
||||
|
||||
/** 打手订单标签概览:订单数与预计/已结算金额。 */
|
||||
export async function getWorkerMyOrderOverview(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
await settleOverdueWorkOrders({ workerId: worker.id, limit: 50 })
|
||||
const rows = await getWorkerOrderOverview(worker.id)
|
||||
const items = rows.map((row) => ({
|
||||
status: row.status,
|
||||
orderCount: row.orderCount,
|
||||
settlementAmount: row.settlementAmount,
|
||||
}))
|
||||
const vipEvidencePending = rows.reduce(
|
||||
(summary, row) => ({
|
||||
orderCount: summary.orderCount + row.vipEvidencePendingCount,
|
||||
settlementAmount: summary.settlementAmount + row.vipEvidencePendingSettlementAmount,
|
||||
}),
|
||||
{ orderCount: 0, settlementAmount: 0 },
|
||||
)
|
||||
const all = items.reduce(
|
||||
(summary, item) => ({
|
||||
orderCount: summary.orderCount + item.orderCount,
|
||||
settlementAmount: summary.settlementAmount + item.settlementAmount,
|
||||
}),
|
||||
{ orderCount: 0, settlementAmount: 0 },
|
||||
)
|
||||
return {
|
||||
items: [
|
||||
...items,
|
||||
{ status: 'vip_evidence_pending', ...vipEvidencePending },
|
||||
{ status: 'all', ...all },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorkerMyOrderEvents(workOrderId: number | string, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
if (runtimeConfig.worker.orderTimelineVisible !== true) {
|
||||
|
||||
@@ -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: '已超时',
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
WorkerLeaderboardResponse,
|
||||
WorkerListResponse,
|
||||
WorkerLoginResponse,
|
||||
WorkerOrderOverviewResponse,
|
||||
WorkerProfileResponse,
|
||||
WorkerUser,
|
||||
WorkerWalletLedger,
|
||||
@@ -131,6 +132,10 @@ export function fetchWorkerMyOrders(params?: Record<string, unknown>) {
|
||||
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/worker/orders', params)
|
||||
}
|
||||
|
||||
export function fetchWorkerMyOrderOverview() {
|
||||
return apiGet<WorkerOrderOverviewResponse>('/api/v1/worker/orders/overview')
|
||||
}
|
||||
|
||||
export function fetchWorkerMyOrderEvents(workOrderId: number) {
|
||||
return apiGet<{ items: WorkOrderEvent[] }>(`/api/v1/worker/orders/${workOrderId}/events`)
|
||||
}
|
||||
|
||||
@@ -159,6 +159,29 @@
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.worker-order-tab-label {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.worker-order-tab-label-main {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.worker-order-tab-settlement {
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.worker-order-tabs .ant-tabs-tab-active .worker-order-tab-settlement {
|
||||
color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.worker-order-tabs .ant-tabs-nav {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -575,6 +598,16 @@
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.worker-hall-load-more {
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
|
||||
@@ -882,16 +915,17 @@ body {
|
||||
|
||||
.worker-orders-status-pill {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #ffffff;
|
||||
color: #475569;
|
||||
padding: 5px 12px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: all 0.15s ease;
|
||||
@@ -919,6 +953,23 @@ body {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.worker-orders-status-pill-main {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-orders-status-pill-settlement {
|
||||
color: #94a3b8;
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.worker-orders-status-pill.active .worker-orders-status-pill-settlement {
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
}
|
||||
|
||||
/* 搜索与快捷刷新栏 */
|
||||
.worker-orders-mobile-search-row {
|
||||
display: flex;
|
||||
|
||||
@@ -323,6 +323,16 @@ export type WorkerListResponse<T> = {
|
||||
pagination: Pagination
|
||||
}
|
||||
|
||||
export type WorkerOrderOverviewItem = {
|
||||
status: string
|
||||
orderCount: number
|
||||
settlementAmount: number
|
||||
}
|
||||
|
||||
export type WorkerOrderOverviewResponse = {
|
||||
items: WorkerOrderOverviewItem[]
|
||||
}
|
||||
|
||||
export type WorkerHallOrdersResponse = {
|
||||
items: WorkOrder[]
|
||||
pagination: Pagination
|
||||
|
||||
Reference in New Issue
Block a user