fix: 修复移动端我的订单卡片与搜索栏Flex样式异常

This commit is contained in:
yml2213
2026-08-16 11:14:33 +08:00
parent 0270657f2b
commit 27bcb7dd68
7 changed files with 430 additions and 77 deletions
@@ -680,6 +680,83 @@ export async function countWorkerAcceptedOrders(workerId: number | string): Prom
return Number(result.rows[0]?.total || 0) return Number(result.rows[0]?.total || 0)
} }
export async function listWorkerCompletionLeaderboard(input: {
limit: number
currentWorkerId: number
}): Promise<
Array<{
rank: number
workerId: number
displayName: string
levelName: string
acceptedOrderCount: number
}>
> {
const limit = Math.min(50, Math.max(1, Math.floor(Number(input.limit || 10))))
const result = await query<{
rank: number
worker_id: number
display_name: string
level_name: string | null
accepted_order_count: number
}>(
`
WITH accepted_totals AS (
SELECT
worker_id,
SUM(accepted_order_count)::int AS accepted_order_count,
MAX(last_accepted_at) AS last_accepted_at
FROM (
SELECT
assigned_worker_id AS worker_id,
COUNT(*)::int AS accepted_order_count,
MAX(accepted_at) AS last_accepted_at
FROM work_orders
WHERE assigned_worker_id IS NOT NULL AND status = 'accepted'
GROUP BY assigned_worker_id
UNION ALL
SELECT
worker_id,
COALESCE(SUM(quantity), 0)::int AS accepted_order_count,
MAX(accepted_at) AS last_accepted_at
FROM work_order_shares
WHERE status = 'accepted'
GROUP BY worker_id
) AS accepted_records
GROUP BY worker_id
),
ranked_workers AS (
SELECT
ROW_NUMBER() OVER (
ORDER BY totals.accepted_order_count DESC, totals.last_accepted_at DESC NULLS LAST, wu.id ASC
)::int AS rank,
wu.id AS worker_id,
COALESCE(NULLIF(wu.display_name, ''), wu.username) AS display_name,
wl.name AS level_name,
totals.accepted_order_count
FROM worker_users wu
INNER JOIN accepted_totals totals ON totals.worker_id = wu.id
LEFT JOIN worker_levels wl ON wl.id = wu.level_id
WHERE wu.status = 'active'
)
SELECT rank, worker_id, display_name, level_name, accepted_order_count
FROM ranked_workers
WHERE rank <= $1 OR worker_id = $2
ORDER BY rank ASC
`,
[limit, Number(input.currentWorkerId)],
)
return result.rows.map((row) => ({
rank: Number(row.rank || 0),
workerId: Number(row.worker_id || 0),
displayName: String(row.display_name || '').trim(),
levelName: String(row.level_name || '').trim(),
acceptedOrderCount: Number(row.accepted_order_count || 0),
}))
}
export async function maybeUpgradeWorkerLevelWithClient( export async function maybeUpgradeWorkerLevelWithClient(
client: PoolClient, client: PoolClient,
workerId: number, workerId: number,
+11
View File
@@ -9,6 +9,7 @@ import {
createWorkerWithdrawRequest, createWorkerWithdrawRequest,
getWorkerProfile, getWorkerProfile,
getWorkerSessionSummary, getWorkerSessionSummary,
getWorkerHallLeaderboard,
grabWorkerHallOrder, grabWorkerHallOrder,
joinWorkerSharingOrder, joinWorkerSharingOrder,
listWorkerProfileFinanceRequests, listWorkerProfileFinanceRequests,
@@ -187,6 +188,16 @@ router.get(
}), }),
) )
router.get(
'/hall/leaderboard',
requireActiveWorker,
createRouteHandler((req) => getWorkerHallLeaderboard(getRequiredWorkerSession(req)), {
successMessage: 'ok',
errorMessage: '读取接单排行榜失败',
scope: '[worker/hall/leaderboard]',
}),
)
router.post( router.post(
'/hall/orders/:workOrderId/grab', '/hall/orders/:workOrderId/grab',
requireActiveWorker, requireActiveWorker,
@@ -44,6 +44,7 @@ import {
listAllWorkCategories, listAllWorkCategories,
listPendingMaterialWorkOrdersByPlatformOrderId, listPendingMaterialWorkOrdersByPlatformOrderId,
listWorkerFinanceRequests, listWorkerFinanceRequests,
listWorkerCompletionLeaderboard,
listWorkerSharesByWorker, listWorkerSharesByWorker,
listWorkerWalletLedgers, listWorkerWalletLedgers,
listWorkerWorkOrderNotes, listWorkerWorkOrderNotes,
@@ -841,6 +842,22 @@ export async function listWorkerHallOrders(query: JsonObject = {}, session: Work
} }
} }
export async function getWorkerHallLeaderboard(session: WorkerSession) {
requireActiveWorkerSession(session)
const entries = await listWorkerCompletionLeaderboard({
limit: 10,
currentWorkerId: session.workerId,
})
const current = entries.find((entry) => entry.workerId === session.workerId)
return {
items: entries.filter((entry) => entry.rank <= 10),
current: {
rank: current?.rank || null,
acceptedOrderCount: current?.acceptedOrderCount || 0,
},
}
}
export async function grabWorkerHallOrder(workOrderId: number | string, session: WorkerSession) { export async function grabWorkerHallOrder(workOrderId: number | string, session: WorkerSession) {
requireActiveWorkerSession(session) requireActiveWorkerSession(session)
const worker = await getRequiredWorker(session.workerId) const worker = await getRequiredWorker(session.workerId)
@@ -1,4 +1,4 @@
import { ClockCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons' import { ClockCircleOutlined, ReloadOutlined, SearchOutlined, TrophyOutlined } from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query' import { useQuery, useQueryClient } from '@tanstack/react-query'
import { import {
Alert, Alert,
@@ -6,6 +6,7 @@ import {
Button, Button,
Card, Card,
Descriptions, Descriptions,
Drawer,
Empty, Empty,
Input, Input,
InputNumber, InputNumber,
@@ -23,6 +24,7 @@ import { useEffect, useState } from 'react'
import { import {
fetchWorkerHallOrders, fetchWorkerHallOrders,
fetchWorkerHallLeaderboard,
fetchWorkerMyOrders, fetchWorkerMyOrders,
fetchWorkerProfile, fetchWorkerProfile,
grabWorkerOrder, grabWorkerOrder,
@@ -47,6 +49,7 @@ export default function WorkerHallPage() {
const [pageSize, setPageSize] = useState(20) const [pageSize, setPageSize] = useState(20)
const [grabbingId, setGrabbingId] = useState(0) const [grabbingId, setGrabbingId] = useState(0)
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null) const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
const [leaderboardOpen, setLeaderboardOpen] = useState(false)
const [sharingQuantity, setSharingQuantity] = useState(1) const [sharingQuantity, setSharingQuantity] = useState(1)
const [submittingSharing, setSubmittingSharing] = useState(false) const [submittingSharing, setSubmittingSharing] = useState(false)
const [autoRefresh, setAutoRefresh] = useState(loadAutoRefreshPreference) const [autoRefresh, setAutoRefresh] = useState(loadAutoRefreshPreference)
@@ -105,6 +108,14 @@ export default function WorkerHallPage() {
retry: false, retry: false,
}) })
const leaderboardQuery = useQuery({
queryKey: ['worker-hall-leaderboard'],
queryFn: () => fetchWorkerHallLeaderboard(),
enabled: leaderboardOpen,
staleTime: 30_000,
retry: false,
})
const orders = ordersQuery.data?.data.items || [] const orders = ordersQuery.data?.data.items || []
const pagination = ordersQuery.data?.data.pagination const pagination = ordersQuery.data?.data.pagination
const categories = ordersQuery.data?.data.categories || [] const categories = ordersQuery.data?.data.categories || []
@@ -125,6 +136,7 @@ export default function WorkerHallPage() {
queryClient.invalidateQueries({ queryKey: ['worker-profile'] }), queryClient.invalidateQueries({ queryKey: ['worker-profile'] }),
queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }), queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }),
queryClient.invalidateQueries({ queryKey: ['worker-my-orders-overview'] }), queryClient.invalidateQueries({ queryKey: ['worker-my-orders-overview'] }),
queryClient.invalidateQueries({ queryKey: ['worker-hall-leaderboard'] }),
]) ])
} }
@@ -219,6 +231,15 @@ export default function WorkerHallPage() {
</div> </div>
<div className="worker-hall-mobile-top-actions"> <div className="worker-hall-mobile-top-actions">
<Tooltip title="接单排行榜">
<Button
aria-label="接单排行榜"
size="small"
shape="circle"
icon={<TrophyOutlined />}
onClick={() => setLeaderboardOpen(true)}
/>
</Tooltip>
<div className="worker-hall-mobile-autorefresh-switch"> <div className="worker-hall-mobile-autorefresh-switch">
<span className="worker-hall-mobile-autorefresh-text"></span> <span className="worker-hall-mobile-autorefresh-text"></span>
<Switch <Switch
@@ -442,6 +463,13 @@ export default function WorkerHallPage() {
<div className="worker-page-head-title-row"> <div className="worker-page-head-title-row">
<Typography.Title level={3}></Typography.Title> <Typography.Title level={3}></Typography.Title>
<div className="worker-hall-head-controls"> <div className="worker-hall-head-controls">
<Tooltip title="接单排行榜">
<Button
aria-label="接单排行榜"
icon={<TrophyOutlined />}
onClick={() => setLeaderboardOpen(true)}
/>
</Tooltip>
<Tooltip <Tooltip
title={ title={
autoRefresh autoRefresh
@@ -727,6 +755,77 @@ export default function WorkerHallPage() {
</Space> </Space>
) : null} ) : null}
</Modal> </Modal>
<Drawer
title={
<Space size={8}>
<TrophyOutlined className="worker-hall-leaderboard-title-icon" />
<span></span>
</Space>
}
open={leaderboardOpen}
placement="right"
width={isMobile ? '88vw' : 360}
onClose={() => setLeaderboardOpen(false)}
>
<div className="worker-hall-leaderboard">
<Typography.Text type="secondary" className="worker-hall-leaderboard-caption">
</Typography.Text>
{leaderboardQuery.isLoading ? (
<div className="worker-hall-leaderboard-loading">
<Spin />
</div>
) : leaderboardQuery.error ? (
<Typography.Text type="danger">
{leaderboardQuery.error instanceof Error
? leaderboardQuery.error.message
: '排行榜读取失败'}
</Typography.Text>
) : (leaderboardQuery.data?.data.items.length || 0) > 0 ? (
<div className="worker-hall-leaderboard-list">
{leaderboardQuery.data?.data.items.map((entry) => (
<div
key={entry.workerId}
className={`worker-hall-leaderboard-row ${
entry.workerId === worker?.workerId ? 'is-current' : ''
}`}
>
<span className={`worker-hall-leaderboard-rank rank-${Math.min(entry.rank, 3)}`}>
{entry.rank}
</span>
<div className="worker-hall-leaderboard-worker">
<Typography.Text strong ellipsis>
{entry.displayName || '未命名打手'}
</Typography.Text>
<Typography.Text type="secondary" ellipsis>
{entry.levelName || '未设置等级'}
</Typography.Text>
</div>
<Typography.Text strong className="worker-hall-leaderboard-count">
{entry.acceptedOrderCount}
</Typography.Text>
</div>
))}
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无完成订单" />
)}
{leaderboardQuery.data ? (
<div className="worker-hall-leaderboard-current">
<Typography.Text type="secondary"></Typography.Text>
<Typography.Text strong>
{leaderboardQuery.data.data.current.rank
? `${leaderboardQuery.data.data.current.rank}`
: '暂未上榜'}
</Typography.Text>
<Typography.Text type="secondary">
{leaderboardQuery.data.data.current.acceptedOrderCount}
</Typography.Text>
</div>
) : null}
</div>
</Drawer>
</> </>
) )
} }
+5
View File
@@ -6,6 +6,7 @@ import type {
WorkOrderShare, WorkOrderShare,
WorkerFinanceRequest, WorkerFinanceRequest,
WorkerHallOrdersResponse, WorkerHallOrdersResponse,
WorkerLeaderboardResponse,
WorkerListResponse, WorkerListResponse,
WorkerLoginResponse, WorkerLoginResponse,
WorkerProfileResponse, WorkerProfileResponse,
@@ -109,6 +110,10 @@ export function fetchWorkerHallOrders(params?: Record<string, unknown>) {
) )
} }
export function fetchWorkerHallLeaderboard() {
return apiGet<WorkerLeaderboardResponse>('/api/v1/worker/hall/leaderboard')
}
export function grabWorkerOrder(workOrderId: number) { export function grabWorkerOrder(workOrderId: number) {
return apiPost<{ order: WorkOrder }>( return apiPost<{ order: WorkOrder }>(
`/api/v1/worker/hall/orders/${workOrderId}/grab`, `/api/v1/worker/hall/orders/${workOrderId}/grab`,
+204 -76
View File
@@ -675,7 +675,7 @@ body {
align-items: center; align-items: center;
gap: 6px; gap: 6px;
overflow-x: auto; overflow-x: auto;
padding-bottom: 2px; padding: 2px 0 4px 0;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
scrollbar-width: none; scrollbar-width: none;
} }
@@ -709,7 +709,7 @@ body {
font-weight: 600; font-weight: 600;
} }
.worker-orders-pill-badge { .worker-orders-status-pill-count {
background: rgba(0, 0, 0, 0.06); background: rgba(0, 0, 0, 0.06);
color: inherit; color: inherit;
font-size: 10px; font-size: 10px;
@@ -718,13 +718,13 @@ body {
line-height: 16px; line-height: 16px;
} }
.worker-orders-status-pill.active .worker-orders-pill-badge { .worker-orders-status-pill.active .worker-orders-status-pill-count {
background: rgba(255, 255, 255, 0.25); background: rgba(255, 255, 255, 0.25);
color: #ffffff; color: #ffffff;
} }
/* 搜索与快捷刷新栏 */ /* 搜索与快捷刷新栏 */
.worker-orders-mobile-search-bar { .worker-orders-mobile-search-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
@@ -739,30 +739,30 @@ body {
.worker-orders-mobile-list { .worker-orders-mobile-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 12px;
width: 100%; width: 100%;
} }
.worker-order-mobile-card { .worker-order-mobile-card {
background: #ffffff; background: #ffffff;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
border-radius: 12px; border-radius: 14px;
padding: 12px; padding: 14px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 10px;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.04); box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
} }
/* 头部:分类 + 订单号 + 状态 */ /* 头部:分类 + 订单号 + 状态 */
.worker-order-mobile-card-top { .worker-order-mobile-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 6px; gap: 8px;
} }
.worker-order-mobile-top-left { .worker-order-mobile-header-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
@@ -770,14 +770,14 @@ body {
flex: 1; flex: 1;
} }
.worker-order-mobile-category-tag { .worker-order-mobile-tag {
margin: 0; margin: 0;
font-size: 11px; font-size: 11px;
line-height: 18px;
padding: 0 6px; padding: 0 6px;
line-height: 18px;
} }
.worker-order-mobile-orderno { .worker-order-mobile-orderno-text {
font-size: 12px; font-size: 12px;
color: #64748b; color: #64748b;
overflow: hidden; overflow: hidden;
@@ -785,27 +785,23 @@ body {
white-space: nowrap; white-space: nowrap;
} }
.worker-order-mobile-top-right { .worker-order-mobile-header-right {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 6px;
flex-shrink: 0; flex-shrink: 0;
} }
.worker-order-mobile-deadline { /* 商品名称与金额 */
font-size: 11px;
}
/* 商品名称与奖励 */
.worker-order-mobile-main-row { .worker-order-mobile-main-row {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 8px; gap: 10px;
} }
.worker-order-mobile-product-title { .worker-order-mobile-product-title {
font-size: 14px; font-size: 15px;
font-weight: 700; font-weight: 700;
color: #0f172a; color: #0f172a;
line-height: 1.35; line-height: 1.35;
@@ -816,52 +812,50 @@ body {
flex: 1; flex: 1;
} }
.worker-order-mobile-reward { .worker-order-mobile-price-badge {
display: flex; display: flex;
flex-direction: column; align-items: baseline;
align-items: flex-end; gap: 4px;
flex-shrink: 0; flex-shrink: 0;
line-height: 1;
} }
.worker-order-mobile-reward-val { .worker-order-mobile-price-label {
font-size: 11px;
color: #64748b;
}
.worker-order-mobile-price-num {
font-size: 18px; font-size: 18px;
font-weight: 800; font-weight: 800;
color: #ea580c; color: #ea580c;
line-height: 1;
} }
.worker-order-mobile-reward-label { /* 辅助信息行 (押金 & 接单时间) */
font-size: 10px; .worker-order-mobile-sub-row {
color: #94a3b8;
margin-top: 2px;
}
/* 辅助信息行 */
.worker-order-mobile-meta-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; justify-content: space-between;
flex-wrap: wrap; gap: 8px;
} font-size: 12px;
.worker-order-mobile-tag {
margin: 0;
font-size: 11px;
padding: 0 6px;
line-height: 18px;
}
.worker-order-mobile-deposit-text {
font-size: 11px;
color: #64748b; color: #64748b;
} }
.worker-order-mobile-sub-item {
color: #64748b;
}
.worker-order-mobile-sub-time {
color: #94a3b8;
font-size: 11px;
}
/* 问题单提示 */ /* 问题单提示 */
.worker-order-mobile-problem-box { .worker-order-mobile-problem-box {
background: #fef2f2; background: #fef2f2;
border: 1px solid #fecaca; border: 1px solid #fecaca;
border-radius: 6px; border-radius: 8px;
padding: 6px 10px; padding: 8px 10px;
color: #b91c1c; color: #b91c1c;
font-size: 12px; font-size: 12px;
line-height: 1.4; line-height: 1.4;
@@ -872,7 +866,7 @@ body {
background: #f8fafc; background: #f8fafc;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
border-radius: 8px; border-radius: 8px;
padding: 8px 10px; padding: 10px 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
@@ -880,7 +874,7 @@ body {
.worker-order-mobile-credentials-grid { .worker-order-mobile-credentials-grid {
display: grid; display: grid;
gap: 3px; gap: 4px;
font-size: 12px; font-size: 12px;
} }
@@ -907,15 +901,15 @@ body {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
margin-top: 2px; margin-top: 4px;
} }
.worker-order-mobile-copy-toolbar { .worker-order-mobile-copy-toolbar {
display: flex; display: flex;
gap: 6px; gap: 8px;
margin-top: 4px; margin-top: 6px;
border-top: 1px dashed #e2e8f0; border-top: 1px dashed #e2e8f0;
padding-top: 6px; padding-top: 8px;
} }
/* 验收凭据条 */ /* 验收凭据条 */
@@ -923,41 +917,69 @@ body {
background: #f0fdf4; background: #f0fdf4;
border: 1px solid #bbf7d0; border: 1px solid #bbf7d0;
border-radius: 8px; border-radius: 8px;
padding: 6px 10px; padding: 8px 10px;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between; gap: 6px;
gap: 8px;
} }
.worker-order-mobile-acceptance-left { .worker-order-mobile-acceptance-files,
.worker-order-mobile-draft-files {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 8px;
flex-wrap: wrap;
}
.worker-order-mobile-acceptance-tag {
font-size: 12px;
color: #166534;
font-weight: 600;
} }
.worker-order-mobile-acceptance-text { .worker-order-mobile-acceptance-text {
font-size: 12px; font-size: 12px;
color: #166534; color: #166534;
font-weight: 500; line-height: 1.35;
} }
.worker-order-mobile-acceptance-time { /* 底部操作与备注行 */
font-size: 11px; .worker-order-mobile-footer-row {
color: #15803d;
}
/* 底部操作行 */
.worker-order-mobile-actions-row {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: space-between;
gap: 8px; gap: 8px;
border-top: 1px solid #f1f5f9; border-top: 1px dashed #f1f5f9;
padding-top: 8px; padding-top: 10px;
margin-top: 2px;
} }
.worker-order-mobile-action-btn { .worker-order-mobile-note-block {
min-width: 0;
flex: 1;
}
.worker-order-mobile-add-note-btn {
border: 1px dashed #cbd5e1;
background: transparent;
color: #64748b;
border-radius: 6px;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
}
.worker-order-mobile-btn-group {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.worker-order-mobile-action-submit {
font-weight: 600; font-weight: 600;
} }
@@ -1626,6 +1648,112 @@ body {
padding: 0 16px; padding: 0 16px;
} }
.worker-hall-leaderboard {
display: flex;
flex-direction: column;
gap: 12px;
}
.worker-hall-leaderboard-title-icon {
color: #d97706;
}
.worker-hall-leaderboard-caption {
font-size: 12px;
}
.worker-hall-leaderboard-loading {
display: flex;
justify-content: center;
padding: 36px 0;
}
.worker-hall-leaderboard-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.worker-hall-leaderboard-row {
display: grid;
grid-template-columns: 30px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
min-height: 56px;
padding: 8px 10px;
border: 1px solid #e2e8f0;
border-radius: 6px;
background: #ffffff;
}
.worker-hall-leaderboard-row.is-current {
border-color: #fb923c;
background: #fff7ed;
}
.worker-hall-leaderboard-rank {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 50%;
background: #f1f5f9;
color: #475569;
font-size: 12px;
font-weight: 700;
}
.worker-hall-leaderboard-rank.rank-1 {
background: #fef3c7;
color: #a16207;
}
.worker-hall-leaderboard-rank.rank-2 {
background: #e2e8f0;
color: #475569;
}
.worker-hall-leaderboard-rank.rank-3 {
background: #fee2e2;
color: #b45309;
}
.worker-hall-leaderboard-worker {
display: flex;
flex-direction: column;
min-width: 0;
gap: 2px;
}
.worker-hall-leaderboard-worker .ant-typography {
display: block;
font-size: 13px;
}
.worker-hall-leaderboard-worker .ant-typography-secondary {
font-size: 11px;
}
.worker-hall-leaderboard-count {
color: #c2410c;
white-space: nowrap;
}
.worker-hall-leaderboard-current {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 4px 12px;
padding-top: 12px;
border-top: 1px solid #e2e8f0;
}
.worker-hall-leaderboard-current .ant-typography:last-child {
grid-column: 1 / -1;
font-size: 12px;
}
/* ===== 打手端响应式断点 ===== */ /* ===== 打手端响应式断点 ===== */
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -291,6 +291,22 @@ export type WorkerHallOrdersResponse = {
visibleDelaySeconds?: number visibleDelaySeconds?: number
} }
export type WorkerLeaderboardEntry = {
rank: number
workerId: number
displayName: string
levelName: string
acceptedOrderCount: number
}
export type WorkerLeaderboardResponse = {
items: WorkerLeaderboardEntry[]
current: {
rank: number | null
acceptedOrderCount: number
}
}
export type CollectField = { export type CollectField = {
key: string key: string
label: string label: string