fix: 修复移动端我的订单卡片与搜索栏Flex样式异常
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user