优化抢单大厅布局
This commit is contained in:
@@ -1,10 +1,36 @@
|
||||
import { ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { EyeOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { App, Button, Card, Empty, Input, Pagination, Space, Spin, Tag, Typography } from 'antd'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
Descriptions,
|
||||
Drawer,
|
||||
Empty,
|
||||
Input,
|
||||
Pagination,
|
||||
Space,
|
||||
Spin,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { fetchWorkerHallOrders, grabWorkerOrder } from '@/services/worker'
|
||||
import type { WorkOrder } from '@/types/worker-platform'
|
||||
import {
|
||||
fetchWorkerHallOrders,
|
||||
fetchWorkerMyOrders,
|
||||
fetchWorkerProfile,
|
||||
grabWorkerOrder,
|
||||
} from '@/services/worker'
|
||||
import type { CollectField, WorkOrder, WorkerUser } from '@/types/worker-platform'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
|
||||
type HallSummary = {
|
||||
activeCount: number
|
||||
pendingAcceptanceCount: number
|
||||
problemCount: number
|
||||
}
|
||||
|
||||
export default function WorkerHallPage() {
|
||||
const { message } = App.useApp()
|
||||
@@ -14,6 +40,7 @@ export default function WorkerHallPage() {
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [grabbingId, setGrabbingId] = useState(0)
|
||||
const [detailOrder, setDetailOrder] = useState<WorkOrder | null>(null)
|
||||
|
||||
const ordersQuery = useQuery({
|
||||
queryKey: ['worker-hall-orders', keyword, page, pageSize],
|
||||
@@ -21,15 +48,82 @@ export default function WorkerHallPage() {
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const profileQuery = useQuery({
|
||||
queryKey: ['worker-profile'],
|
||||
queryFn: () => fetchWorkerProfile(),
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const hallSummaryQuery = useQuery({
|
||||
queryKey: ['worker-hall-summary'],
|
||||
queryFn: async () => {
|
||||
const [inProgressResponse, pendingAcceptanceResponse, problemResponse] =
|
||||
await Promise.all([
|
||||
fetchWorkerMyOrders({
|
||||
status: 'in_progress',
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
}),
|
||||
fetchWorkerMyOrders({
|
||||
status: 'pending_acceptance',
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
}),
|
||||
fetchWorkerMyOrders({
|
||||
status: 'problem',
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
}),
|
||||
])
|
||||
|
||||
const pendingAcceptanceCount =
|
||||
pendingAcceptanceResponse.data.pagination.total
|
||||
const problemCount = problemResponse.data.pagination.total
|
||||
|
||||
return {
|
||||
activeCount:
|
||||
inProgressResponse.data.pagination.total +
|
||||
pendingAcceptanceCount +
|
||||
problemCount,
|
||||
pendingAcceptanceCount,
|
||||
problemCount,
|
||||
} satisfies HallSummary
|
||||
},
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const orders = ordersQuery.data?.data.items || []
|
||||
const pagination = ordersQuery.data?.data.pagination
|
||||
const worker = profileQuery.data?.data.worker
|
||||
const activeCount = hallSummaryQuery.data?.activeCount || 0
|
||||
const pendingAcceptanceCount = hallSummaryQuery.data?.pendingAcceptanceCount || 0
|
||||
const problemCount = hallSummaryQuery.data?.problemCount || 0
|
||||
const maxActiveOrders = Number(worker?.level?.permissions.maxActiveOrders || 0)
|
||||
const remainingSlots =
|
||||
maxActiveOrders > 0 ? Math.max(0, maxActiveOrders - activeCount) : null
|
||||
const shouldWarn =
|
||||
(remainingSlots !== null && remainingSlots <= 0) || problemCount > 0
|
||||
const detailDisabledReason = detailOrder
|
||||
? resolveGrabDisabledReason(detailOrder, worker, remainingSlots)
|
||||
: ''
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-hall-orders'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-hall-summary'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-profile'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['worker-my-orders-overview'] }),
|
||||
])
|
||||
}
|
||||
|
||||
async function submitGrab(order: WorkOrder) {
|
||||
setGrabbingId(order.workOrderId)
|
||||
try {
|
||||
await grabWorkerOrder(order.workOrderId)
|
||||
message.success('抢单成功')
|
||||
await queryClient.invalidateQueries({ queryKey: ['worker-hall-orders'] })
|
||||
message.success('抢单成功,已进入我的订单')
|
||||
setDetailOrder(null)
|
||||
await refreshAll()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '抢单失败')
|
||||
} finally {
|
||||
@@ -42,103 +136,415 @@ export default function WorkerHallPage() {
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const summaryItems = [
|
||||
{
|
||||
label: '当前余额',
|
||||
value: worker ? formatMoney(worker.wallet.availableAmount) : '-',
|
||||
note: '可用于冻结押金',
|
||||
},
|
||||
{
|
||||
label: '接单上限',
|
||||
value:
|
||||
remainingSlots === null
|
||||
? '未配置'
|
||||
: `${activeCount}/${maxActiveOrders} 单`,
|
||||
note:
|
||||
remainingSlots === null
|
||||
? '当前等级待配置'
|
||||
: `还能再接 ${remainingSlots} 单`,
|
||||
},
|
||||
{
|
||||
label: '冻结押金',
|
||||
value: worker ? formatMoney(worker.wallet.frozenDepositAmount) : '-',
|
||||
note: '当前已占用金额',
|
||||
},
|
||||
{
|
||||
label: '问题单',
|
||||
value: `${problemCount} 单`,
|
||||
note: problemCount ? '建议优先处理' : '当前暂无问题单',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<section className="page-stack">
|
||||
<div className="worker-page-head">
|
||||
<Typography.Title level={3}>抢单大厅</Typography.Title>
|
||||
<Space.Compact>
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索商品或订单号"
|
||||
prefix={<SearchOutlined />}
|
||||
onChange={(event) => {
|
||||
const nextKeyword = event.target.value
|
||||
setKeywordInput(nextKeyword)
|
||||
if (!nextKeyword.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
loading={ordersQuery.isFetching}
|
||||
onClick={applyKeywordSearch}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Space wrap className="worker-hall-toolbar">
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={ordersQuery.isFetching}
|
||||
onClick={() => ordersQuery.refetch()}
|
||||
loading={
|
||||
ordersQuery.isFetching ||
|
||||
profileQuery.isFetching ||
|
||||
hallSummaryQuery.isFetching
|
||||
}
|
||||
onClick={() => refreshAll()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact className="worker-hall-search">
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索商品名称 / 订单号 / 工单号"
|
||||
prefix={<SearchOutlined />}
|
||||
onChange={(event) => {
|
||||
const nextKeyword = event.target.value
|
||||
setKeywordInput(nextKeyword)
|
||||
if (!nextKeyword.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
loading={ordersQuery.isFetching}
|
||||
onClick={applyKeywordSearch}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{ordersQuery.isLoading ? <Spin /> : null}
|
||||
{ordersQuery.error ? (
|
||||
<Card>
|
||||
<Typography.Text type="danger">
|
||||
{ordersQuery.error instanceof Error ? ordersQuery.error.message : '读取抢单大厅失败'}
|
||||
</Typography.Text>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{!ordersQuery.isLoading && orders.length === 0 ? (
|
||||
<Empty description="暂无可抢订单" />
|
||||
) : (
|
||||
<div className="worker-order-grid">
|
||||
{orders.map((order) => (
|
||||
<Card key={order.workOrderId} className="worker-order-card" bordered={false}>
|
||||
<Space direction="vertical" className="full-width" size={8}>
|
||||
<Space className="worker-order-card-head">
|
||||
<Typography.Text strong ellipsis>
|
||||
{order.productName}
|
||||
</Typography.Text>
|
||||
<Tag color="blue">{order.categoryName || '默认分类'}</Tag>
|
||||
</Space>
|
||||
<Typography.Text type="secondary">订单号:{order.platformOrderId}</Typography.Text>
|
||||
<Typography.Title level={4} className="worker-order-money">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
所需冻结押金:{formatMoney(order.freezeDepositAmount)}
|
||||
</Typography.Text>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => submitGrab(order)}
|
||||
>
|
||||
立即抢单
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
<Card className="worker-hall-board-card" bordered={false}>
|
||||
<div className="worker-hall-summary-strip">
|
||||
{summaryItems.map((item) => (
|
||||
<div key={item.label} className="worker-hall-summary-item">
|
||||
<span className="worker-hall-summary-label">{item.label}</span>
|
||||
<strong className="worker-hall-summary-value">{item.value}</strong>
|
||||
<small className="worker-hall-summary-note">{item.note}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(pagination?.total || 0) > 0 ? (
|
||||
<Pagination
|
||||
current={pagination?.page || page}
|
||||
pageSize={pagination?.pageSize || pageSize}
|
||||
total={pagination?.total || 0}
|
||||
showSizeChanger
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showTotal={(total) => `共 ${total} 条`}
|
||||
onChange={(nextPage, nextPageSize) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{shouldWarn ? (
|
||||
<Alert
|
||||
className="worker-hall-warning"
|
||||
showIcon
|
||||
type="warning"
|
||||
message={
|
||||
remainingSlots !== null && remainingSlots <= 0
|
||||
? '当前已达到接单上限,请先处理现有工单'
|
||||
: `当前还有 ${problemCount} 单问题单待处理,建议优先收口`
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{ordersQuery.isLoading ? (
|
||||
<div className="worker-hall-loading">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : ordersQuery.error ? (
|
||||
<Empty
|
||||
description={
|
||||
ordersQuery.error instanceof Error
|
||||
? ordersQuery.error.message
|
||||
: '读取抢单大厅失败'
|
||||
}
|
||||
/>
|
||||
) : orders.length === 0 ? (
|
||||
<Empty
|
||||
description={
|
||||
keyword
|
||||
? `没有找到与 “${keyword}” 相关的可抢工单`
|
||||
: '当前暂无可抢订单,可稍后刷新再看'
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="worker-hall-card-grid">
|
||||
{orders.map((order) => {
|
||||
const requirementFields = getRequirementFields(order)
|
||||
const disabledReason = resolveGrabDisabledReason(
|
||||
order,
|
||||
worker,
|
||||
remainingSlots,
|
||||
)
|
||||
const zeroDeposit = Number(order.freezeDepositAmount || 0) <= 0
|
||||
|
||||
return (
|
||||
<article key={order.workOrderId} className="worker-hall-card">
|
||||
<div className="worker-hall-card-head">
|
||||
<div className="worker-hall-card-title">
|
||||
<Typography.Link onClick={() => setDetailOrder(order)}>
|
||||
{order.productName || order.workOrderNo}
|
||||
</Typography.Link>
|
||||
<Typography.Text type="secondary">
|
||||
{order.categoryName || '默认分类'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="worker-hall-card-badges">
|
||||
<Tag color={zeroDeposit ? 'green' : 'gold'}>
|
||||
{zeroDeposit ? '免押金' : '需押金'}
|
||||
</Tag>
|
||||
<Tag color="blue">
|
||||
{formatRequirementShortLabel(requirementFields)}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-order">
|
||||
<Typography.Text type="secondary">
|
||||
工单号:{order.workOrderNo}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
订单号:{order.platformOrderId || '-'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-main">
|
||||
<div className="worker-hall-card-amount">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
</div>
|
||||
<div className="worker-hall-card-side">
|
||||
<span>所需保证金:{formatMoney(order.freezeDepositAmount)}</span>
|
||||
<span>发布时间:{formatDateTime(resolvePublishedTime(order))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-meta">
|
||||
<div className="worker-hall-card-meta-row">
|
||||
<span>资料要求</span>
|
||||
<strong>{formatRequirementPreview(requirementFields)}</strong>
|
||||
</div>
|
||||
<div className="worker-hall-card-meta-row">
|
||||
<span>抢单判断</span>
|
||||
<strong
|
||||
className={
|
||||
disabledReason
|
||||
? 'worker-hall-card-danger'
|
||||
: 'worker-hall-card-safe'
|
||||
}
|
||||
>
|
||||
{disabledReason || buildGrabReadyHint(order)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-card-actions">
|
||||
<Button icon={<EyeOutlined />} onClick={() => setDetailOrder(order)}>
|
||||
详情
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={Boolean(disabledReason)}
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => submitGrab(order)}
|
||||
>
|
||||
立即抢单
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{(pagination?.total || 0) > 0 ? (
|
||||
<div className="worker-hall-pagination">
|
||||
<Pagination
|
||||
current={pagination?.page || page}
|
||||
pageSize={pagination?.pageSize || pageSize}
|
||||
total={pagination?.total || 0}
|
||||
showSizeChanger
|
||||
pageSizeOptions={[20, 50, 100]}
|
||||
showTotal={(total) => `共 ${total} 条`}
|
||||
onChange={(nextPage, nextPageSize) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Drawer
|
||||
title={
|
||||
detailOrder
|
||||
? `${detailOrder.productName || '工单详情'} · ${detailOrder.workOrderNo}`
|
||||
: '工单详情'
|
||||
}
|
||||
width={760}
|
||||
open={Boolean(detailOrder)}
|
||||
destroyOnHidden
|
||||
onClose={() => setDetailOrder(null)}
|
||||
extra={
|
||||
detailOrder ? (
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={Boolean(detailDisabledReason)}
|
||||
loading={grabbingId === detailOrder.workOrderId}
|
||||
onClick={() => submitGrab(detailOrder)}
|
||||
>
|
||||
立即抢单
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{detailOrder ? (
|
||||
<div className="worker-hall-detail-stack">
|
||||
<Alert
|
||||
showIcon
|
||||
type={detailDisabledReason ? 'warning' : 'success'}
|
||||
message={detailDisabledReason || buildGrabReadyHint(detailOrder)}
|
||||
/>
|
||||
|
||||
<Card title="基础信息" size="small">
|
||||
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
|
||||
<Descriptions.Item label="商品">
|
||||
{detailOrder.productName || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="分类">
|
||||
{detailOrder.categoryName || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="工单号">
|
||||
{detailOrder.workOrderNo}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="平台订单号">
|
||||
{detailOrder.platformOrderId || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="接单奖励">
|
||||
{formatMoney(detailOrder.rewardAmount)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="冻结押金">
|
||||
{formatMoney(detailOrder.freezeDepositAmount)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="发布时间">
|
||||
{formatDateTime(resolvePublishedTime(detailOrder))}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">
|
||||
{formatDateTime(detailOrder.updatedAt)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="资料要求">
|
||||
{formatRequirementShortLabel(getRequirementFields(detailOrder))}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="接单资格">
|
||||
{detailDisabledReason || '当前可直接抢单'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
<Card title="资料要求" size="small">
|
||||
{renderRequirementFieldValues(detailOrder)}
|
||||
</Card>
|
||||
</div>
|
||||
) : null}
|
||||
</Drawer>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function getRequirementFields(order: WorkOrder | null): CollectField[] {
|
||||
if (!order) return []
|
||||
const rawFields = Array.isArray(order.requirement?.fields)
|
||||
? order.requirement.fields
|
||||
: []
|
||||
return rawFields
|
||||
.map((item) => {
|
||||
const source = asRecord(item)
|
||||
const key = String(source.key || '').trim()
|
||||
if (!key) return null
|
||||
return {
|
||||
key,
|
||||
label: String(source.label || key).trim(),
|
||||
required: source.required !== false,
|
||||
}
|
||||
})
|
||||
.filter((item): item is CollectField => Boolean(item))
|
||||
}
|
||||
|
||||
function renderRequirementFieldValues(order: WorkOrder) {
|
||||
const fields = getRequirementFields(order)
|
||||
if (fields.length === 0) {
|
||||
return <Typography.Text type="secondary">当前工单未配置资料要求</Typography.Text>
|
||||
}
|
||||
|
||||
return (
|
||||
<Descriptions column={1} bordered size="small">
|
||||
{fields.map((field) => (
|
||||
<Descriptions.Item key={field.key} label={field.label}>
|
||||
<Space size={6} wrap>
|
||||
<Typography.Text code>{field.key}</Typography.Text>
|
||||
<Tag color={field.required ? 'orange' : 'default'}>
|
||||
{field.required ? '必填' : '选填'}
|
||||
</Tag>
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
)
|
||||
}
|
||||
|
||||
function resolveGrabDisabledReason(
|
||||
order: WorkOrder,
|
||||
worker: WorkerUser | undefined,
|
||||
remainingSlots: number | null,
|
||||
) {
|
||||
if (remainingSlots !== null && remainingSlots <= 0) {
|
||||
return '已达到当前等级最大同时接单量'
|
||||
}
|
||||
|
||||
if (!worker) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const availableAmount = Number(worker.wallet.availableAmount || 0)
|
||||
const freezeAmount = Number(order.freezeDepositAmount || 0)
|
||||
if (availableAmount < freezeAmount) {
|
||||
return `余额不足,还差 ${formatMoney(freezeAmount - availableAmount)}`
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function buildGrabReadyHint(order: WorkOrder) {
|
||||
const freezeAmount = Number(order.freezeDepositAmount || 0)
|
||||
if (freezeAmount <= 0) {
|
||||
return '当前工单无需冻结押金,可直接抢单'
|
||||
}
|
||||
return `抢单后会冻结 ${formatMoney(freezeAmount)}`
|
||||
}
|
||||
|
||||
function formatRequirementShortLabel(fields: CollectField[]) {
|
||||
if (fields.length === 0) {
|
||||
return '资料简单'
|
||||
}
|
||||
const requiredCount = fields.filter((field) => field.required).length
|
||||
return `${requiredCount} 项必填 / 共 ${fields.length} 项`
|
||||
}
|
||||
|
||||
function formatRequirementPreview(fields: CollectField[]) {
|
||||
if (fields.length === 0) {
|
||||
return '当前工单未配置额外资料字段'
|
||||
}
|
||||
|
||||
const preview = fields
|
||||
.slice(0, 3)
|
||||
.map((field) => field.label)
|
||||
.filter(Boolean)
|
||||
.join('、')
|
||||
|
||||
if (fields.length <= 3) {
|
||||
return preview
|
||||
}
|
||||
|
||||
return `${preview} 等 ${fields.length} 项`
|
||||
}
|
||||
|
||||
function resolvePublishedTime(order: WorkOrder) {
|
||||
return order.publishedAt || order.createdAt || order.updatedAt
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {}
|
||||
}
|
||||
|
||||
function formatMoney(value: number | undefined) {
|
||||
return `¥${((Number(value || 0) || 0) / 100).toFixed(2)}`
|
||||
}
|
||||
|
||||
@@ -1475,6 +1475,27 @@ select {
|
||||
.table-toolbar {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.worker-profile-hero-head {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-hall-toolbar {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.worker-hall-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.worker-hall-summary-strip {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.worker-hall-card-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
@@ -1668,24 +1689,177 @@ select {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.worker-order-grid {
|
||||
.worker-hall-head-copy {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-hall-head-copy .ant-typography {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.worker-hall-toolbar {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.worker-hall-search {
|
||||
width: min(460px, 100%);
|
||||
}
|
||||
|
||||
.worker-hall-board-card .ant-card-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.worker-order-card .ant-card-body {
|
||||
height: 100%;
|
||||
.worker-hall-summary-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.worker-order-card-head {
|
||||
width: 100%;
|
||||
.worker-hall-summary-item {
|
||||
min-width: 0;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-hall-summary-label {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-summary-value {
|
||||
color: #0f172a;
|
||||
font-size: 22px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.worker-hall-summary-note {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.worker-hall-warning {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.worker-hall-loading {
|
||||
min-height: 220px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.worker-hall-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.worker-hall-card {
|
||||
min-width: 0;
|
||||
padding: 18px 18px 16px;
|
||||
border-radius: 16px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #edf0f5;
|
||||
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.worker-hall-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.worker-order-money {
|
||||
margin: 4px 0 0;
|
||||
.worker-hall-card-title {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.worker-hall-card-title .ant-typography {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.worker-hall-card-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.worker-hall-card-order {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.worker-hall-card-main {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.worker-hall-card-amount {
|
||||
color: #f97316;
|
||||
font-size: 34px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.worker-hall-card-side {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta-row {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta-row span {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.worker-hall-card-meta-row strong {
|
||||
color: #334155;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.worker-hall-card-danger {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
.worker-hall-card-safe {
|
||||
color: #16a34a !important;
|
||||
}
|
||||
|
||||
.worker-hall-card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.worker-hall-detail-stack {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.worker-orders-overview-grid {
|
||||
@@ -2079,12 +2253,32 @@ select {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-order-grid {
|
||||
.worker-hall-summary-strip {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.worker-hall-toolbar,
|
||||
.worker-orders-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-hall-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.worker-hall-card-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.worker-hall-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.worker-hall-card-head,
|
||||
.worker-hall-card-actions {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-orders-search {
|
||||
@@ -2096,10 +2290,6 @@ select {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.worker-profile-hero-head {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.worker-profile-hero-side {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user