拆分打手订单列表
This commit is contained in:
@@ -19,8 +19,11 @@ export function WorkerOrderDesktopList({
|
||||
keyword,
|
||||
orders,
|
||||
pagination,
|
||||
page,
|
||||
pageSize,
|
||||
columns,
|
||||
loading,
|
||||
searching,
|
||||
refreshing,
|
||||
error,
|
||||
onRefresh,
|
||||
@@ -36,8 +39,11 @@ export function WorkerOrderDesktopList({
|
||||
keyword: string
|
||||
orders: WorkOrder[]
|
||||
pagination: Pagination | undefined
|
||||
page: number
|
||||
pageSize: number
|
||||
columns: TableColumnsType<WorkOrder>
|
||||
loading: boolean
|
||||
searching: boolean
|
||||
refreshing: boolean
|
||||
error: unknown
|
||||
onRefresh: () => void
|
||||
@@ -91,7 +97,7 @@ export function WorkerOrderDesktopList({
|
||||
onChange={(event) => onKeywordInputChange(event.target.value)}
|
||||
onPressEnter={onKeywordSearch}
|
||||
/>
|
||||
<Button icon={<SearchOutlined />} loading={refreshing} onClick={onKeywordSearch}>
|
||||
<Button icon={<SearchOutlined />} loading={searching} onClick={onKeywordSearch}>
|
||||
搜索
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
@@ -114,8 +120,8 @@ export function WorkerOrderDesktopList({
|
||||
),
|
||||
}}
|
||||
pagination={{
|
||||
current: pagination?.page || 1,
|
||||
pageSize: pagination?.pageSize || 20,
|
||||
current: pagination?.page || page,
|
||||
pageSize: pagination?.pageSize || pageSize,
|
||||
total: pagination?.total || 0,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
import {
|
||||
CopyOutlined,
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
MessageOutlined,
|
||||
PictureOutlined,
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { Button, Empty, Input, Pagination, Spin, Tag } from 'antd'
|
||||
|
||||
import { DeadlineCountdown } from '@/components/DeadlineCountdown'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import type {
|
||||
Pagination as PaginationData,
|
||||
WorkerOrderOverviewItem,
|
||||
WorkOrder,
|
||||
} from '@/types/worker-platform'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
|
||||
import type { WorkerOrderStatusOption } from './WorkerOrderDesktopList'
|
||||
import {
|
||||
canEditAcceptanceImages,
|
||||
canRemindAcceptance,
|
||||
canRequestCancel,
|
||||
canSubmitAcceptance,
|
||||
canSubmitFeedback,
|
||||
formatMoney,
|
||||
formatStatus,
|
||||
getCopyOrderInfoItems,
|
||||
getDisplayOrderNo,
|
||||
getMaterialDetailItems,
|
||||
getMaterialScreenshots,
|
||||
getSettlementLabel,
|
||||
getUniqueAcceptanceFiles,
|
||||
getWorkerAcceptance,
|
||||
getWorkerDraft,
|
||||
hasSubmittedAcceptance,
|
||||
isVipEvidencePending,
|
||||
resolveStatusColor,
|
||||
} from './worker-order-utils'
|
||||
|
||||
export function WorkerOrderMobileList({
|
||||
statusOptions,
|
||||
status,
|
||||
overviewByStatus,
|
||||
keywordInput,
|
||||
keyword,
|
||||
orders,
|
||||
pagination,
|
||||
page,
|
||||
pageSize,
|
||||
loading,
|
||||
searching,
|
||||
refreshing,
|
||||
error,
|
||||
onStatusChange,
|
||||
onKeywordInputChange,
|
||||
onKeywordSearch,
|
||||
onRefresh,
|
||||
onPageChange,
|
||||
onOpenDetail,
|
||||
onOpenAcceptance,
|
||||
onRemindAcceptance,
|
||||
onOpenCancelRequest,
|
||||
onOpenFeedback,
|
||||
onOpenNote,
|
||||
onCopyOrderInfo,
|
||||
onCopyMaterialImages,
|
||||
}: {
|
||||
statusOptions: readonly WorkerOrderStatusOption[]
|
||||
status: string
|
||||
overviewByStatus: Map<string, WorkerOrderOverviewItem>
|
||||
keywordInput: string
|
||||
keyword: string
|
||||
orders: WorkOrder[]
|
||||
pagination: PaginationData | undefined
|
||||
page: number
|
||||
pageSize: number
|
||||
loading: boolean
|
||||
searching: boolean
|
||||
refreshing: boolean
|
||||
error: unknown
|
||||
onStatusChange: (status: string) => void
|
||||
onKeywordInputChange: (keyword: string) => void
|
||||
onKeywordSearch: () => void
|
||||
onRefresh: () => void
|
||||
onPageChange: (page: number, pageSize: number) => void
|
||||
onOpenDetail: (order: WorkOrder) => void
|
||||
onOpenAcceptance: (order: WorkOrder) => void
|
||||
onRemindAcceptance: (order: WorkOrder) => void
|
||||
onOpenCancelRequest: (order: WorkOrder) => void
|
||||
onOpenFeedback: (order: WorkOrder) => void
|
||||
onOpenNote: (order: WorkOrder) => void
|
||||
onCopyOrderInfo: (order: WorkOrder) => void
|
||||
onCopyMaterialImages: (order: WorkOrder) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="worker-orders-mobile-page">
|
||||
<div className="worker-orders-mobile-status-bar">
|
||||
{statusOptions.map((item) => {
|
||||
const overview = overviewByStatus.get(item.value || 'all')
|
||||
const count = Number(overview?.orderCount || 0)
|
||||
return (
|
||||
<button
|
||||
key={item.value || 'all'}
|
||||
type="button"
|
||||
className={`worker-orders-status-pill ${(status || '') === item.value ? 'active' : ''}`}
|
||||
onClick={() => onStatusChange(item.value)}
|
||||
>
|
||||
<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">
|
||||
{getSettlementLabel(item.value)} {formatMoney(overview?.settlementAmount)}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="worker-orders-mobile-search-row">
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索订单号 / 商品名 / 游戏ID / 游戏昵称"
|
||||
prefix={<SearchOutlined />}
|
||||
className="worker-orders-mobile-search-input"
|
||||
onChange={(event) => onKeywordInputChange(event.target.value)}
|
||||
onPressEnter={onKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
loading={searching}
|
||||
onClick={onKeywordSearch}
|
||||
/>
|
||||
<Button icon={<ReloadOutlined />} loading={refreshing} onClick={onRefresh} title="刷新" />
|
||||
</div>
|
||||
|
||||
<div className="worker-orders-mobile-list">
|
||||
{loading ? (
|
||||
<div className="worker-hall-loading">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<Empty description={error instanceof Error ? error.message : '读取我的订单失败'} />
|
||||
) : orders.length === 0 ? (
|
||||
<Empty
|
||||
description={keyword ? `没有找到与 “${keyword}” 相关的订单` : '当前筛选下暂无订单'}
|
||||
/>
|
||||
) : (
|
||||
orders.map((order) => (
|
||||
<WorkerOrderMobileCard
|
||||
key={order.workOrderId}
|
||||
order={order}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onOpenAcceptance={onOpenAcceptance}
|
||||
onRemindAcceptance={onRemindAcceptance}
|
||||
onOpenCancelRequest={onOpenCancelRequest}
|
||||
onOpenFeedback={onOpenFeedback}
|
||||
onOpenNote={onOpenNote}
|
||||
onCopyOrderInfo={onCopyOrderInfo}
|
||||
onCopyMaterialImages={onCopyMaterialImages}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
{(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={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkerOrderMobileCard({
|
||||
order,
|
||||
onOpenDetail,
|
||||
onOpenAcceptance,
|
||||
onRemindAcceptance,
|
||||
onOpenCancelRequest,
|
||||
onOpenFeedback,
|
||||
onOpenNote,
|
||||
onCopyOrderInfo,
|
||||
onCopyMaterialImages,
|
||||
}: Omit<
|
||||
Parameters<typeof WorkerOrderMobileList>[0],
|
||||
| 'statusOptions'
|
||||
| 'status'
|
||||
| 'overviewByStatus'
|
||||
| 'keywordInput'
|
||||
| 'keyword'
|
||||
| 'orders'
|
||||
| 'pagination'
|
||||
| 'page'
|
||||
| 'pageSize'
|
||||
| 'loading'
|
||||
| 'searching'
|
||||
| 'refreshing'
|
||||
| 'error'
|
||||
| 'onStatusChange'
|
||||
| 'onKeywordInputChange'
|
||||
| 'onKeywordSearch'
|
||||
| 'onRefresh'
|
||||
| 'onPageChange'
|
||||
> & { order: WorkOrder }) {
|
||||
const items = getMaterialDetailItems(order).filter((item) => String(item.value || '').trim())
|
||||
const screenshots = getMaterialScreenshots(order)
|
||||
const acceptanceFiles = getUniqueAcceptanceFiles(getWorkerAcceptance(order))
|
||||
const draftFiles = getUniqueAcceptanceFiles(getWorkerDraft(order))
|
||||
const noteText = getWorkerAcceptance(order)?.note
|
||||
const workerNote = String(order.workerNote || '').trim()
|
||||
|
||||
return (
|
||||
<article className="worker-order-mobile-card">
|
||||
<div className="worker-order-mobile-header">
|
||||
<div className="worker-order-mobile-header-left">
|
||||
{order.isUnread ? (
|
||||
<Tag color="purple" className="worker-order-unread-tag">
|
||||
新
|
||||
</Tag>
|
||||
) : null}
|
||||
{order.categoryName ? (
|
||||
<Tag color="blue" className="worker-order-mobile-tag">
|
||||
{order.categoryName}
|
||||
</Tag>
|
||||
) : null}
|
||||
{order.myShare ? (
|
||||
<Tag color="purple" className="worker-order-mobile-tag">
|
||||
拼单
|
||||
</Tag>
|
||||
) : null}
|
||||
<span className="worker-order-mobile-orderno-text">{getDisplayOrderNo(order)}</span>
|
||||
</div>
|
||||
<div className="worker-order-mobile-header-right">
|
||||
<Tag color={resolveStatusColor(order.status)}>{formatStatus(order.status)}</Tag>
|
||||
{order.status === 'in_progress' && order.deadlineAt ? (
|
||||
<DeadlineCountdown deadlineAt={order.deadlineAt} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="worker-order-mobile-main-row">
|
||||
<div className="worker-order-mobile-product-title">{order.productName || '未命名商品'}</div>
|
||||
<div className="worker-order-mobile-price-badge">
|
||||
<span className="worker-order-mobile-price-label">报酬</span>
|
||||
<strong className="worker-order-mobile-price-num">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="worker-order-mobile-sub-row">
|
||||
<span className="worker-order-mobile-sub-item">
|
||||
押金: {formatMoney(order.freezeDepositAmount)}
|
||||
</span>
|
||||
{order.myShare ? (
|
||||
<span className="worker-order-mobile-sub-item">
|
||||
已拼 {order.myShare.quantity} 份 ({formatMoney(order.myShare.shareReward)})
|
||||
</span>
|
||||
) : null}
|
||||
<span className="worker-order-mobile-sub-time">{formatDateTime(order.assignedAt)}</span>
|
||||
</div>
|
||||
|
||||
{order.status === 'problem' && String(order.problemNote || '').trim() ? (
|
||||
<div className="worker-order-mobile-problem-box">
|
||||
<strong>问题:</strong>
|
||||
<span>{order.problemNote}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{items.length > 0 || screenshots.length > 0 ? (
|
||||
<div className="worker-order-mobile-credentials-box">
|
||||
{items.length > 0 ? (
|
||||
<div className="worker-order-mobile-credentials-grid">
|
||||
{items.map((item) => (
|
||||
<div key={item.key} className="worker-order-mobile-credential-line">
|
||||
<span className="worker-order-mobile-credential-label">{item.label}:</span>
|
||||
<span className="worker-order-mobile-credential-val">{item.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{screenshots.length > 0 ? (
|
||||
<div className="worker-order-mobile-screenshots-row">
|
||||
<span className="worker-order-mobile-credential-label">截图:</span>
|
||||
<ImagePreviewList files={screenshots} size={34} />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="worker-order-mobile-copy-toolbar">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
disabled={getCopyOrderInfoItems(order).length === 0}
|
||||
onClick={() => onCopyOrderInfo(order)}
|
||||
>
|
||||
复制资料
|
||||
</Button>
|
||||
{screenshots.length > 0 ? (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<PictureOutlined />}
|
||||
onClick={() => onCopyMaterialImages(order)}
|
||||
>
|
||||
复制图片
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{acceptanceFiles.length > 0 ||
|
||||
(draftFiles.length > 0 && !hasSubmittedAcceptance(order)) ||
|
||||
noteText ? (
|
||||
<div className="worker-order-mobile-acceptance-strip">
|
||||
{acceptanceFiles.length > 0 ? (
|
||||
<div className="worker-order-mobile-acceptance-files">
|
||||
<span className="worker-order-mobile-acceptance-tag">
|
||||
已提交凭据 ({acceptanceFiles.length}张)
|
||||
</span>
|
||||
<ImagePreviewList files={acceptanceFiles} size={32} maxVisible={3} />
|
||||
</div>
|
||||
) : null}
|
||||
{isVipEvidencePending(order) ? <Tag color="purple">VIP 免审核,待补图</Tag> : null}
|
||||
{draftFiles.length > 0 && !hasSubmittedAcceptance(order) ? (
|
||||
<div className="worker-order-mobile-draft-files">
|
||||
<Tag color="processing" icon={<PictureOutlined />}>
|
||||
已暂存 {draftFiles.length} 张图片
|
||||
</Tag>
|
||||
<ImagePreviewList files={draftFiles} size={32} maxVisible={3} />
|
||||
</div>
|
||||
) : null}
|
||||
{noteText ? (
|
||||
<div className="worker-order-mobile-acceptance-text">说明:{noteText}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="worker-order-mobile-footer-row">
|
||||
<div className="worker-order-mobile-note-block">
|
||||
{workerNote ? (
|
||||
<div
|
||||
className={`worker-note-badge theme-${resolveNoteTheme(workerNote)}`}
|
||||
onClick={() => onOpenNote(order)}
|
||||
>
|
||||
<span className="worker-note-text" title={workerNote}>
|
||||
{workerNote}
|
||||
</span>
|
||||
<EditOutlined className="worker-note-edit-icon" />
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="worker-order-mobile-add-note-btn"
|
||||
onClick={() => onOpenNote(order)}
|
||||
>
|
||||
<EditOutlined /> 添加备注
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="worker-order-mobile-btn-group">
|
||||
<Button size="small" icon={<EyeOutlined />} onClick={() => onOpenDetail(order)}>
|
||||
详情
|
||||
</Button>
|
||||
{canEditAcceptanceImages(order) ? (
|
||||
<Button
|
||||
size="small"
|
||||
type={canSubmitAcceptance(order) ? 'primary' : 'default'}
|
||||
icon={<PictureOutlined />}
|
||||
className="worker-order-mobile-action-submit"
|
||||
onClick={() => onOpenAcceptance(order)}
|
||||
>
|
||||
{hasSubmittedAcceptance(order) ? '补充图片' : '提交验收'}
|
||||
</Button>
|
||||
) : null}
|
||||
{canRemindAcceptance(order) ? (
|
||||
<Button size="small" onClick={() => onRemindAcceptance(order)}>
|
||||
催验收
|
||||
</Button>
|
||||
) : null}
|
||||
{canRequestCancel(order) ? (
|
||||
<Button size="small" danger onClick={() => onOpenCancelRequest(order)}>
|
||||
申请取消
|
||||
</Button>
|
||||
) : null}
|
||||
{canSubmitFeedback(order) ? (
|
||||
<Button size="small" icon={<MessageOutlined />} onClick={() => onOpenFeedback(order)}>
|
||||
反馈
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
function resolveNoteTheme(note?: string) {
|
||||
const text = String(note || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (!text) return 'default'
|
||||
if (
|
||||
['问题', '异常', '密码', '封号', '错误', '无法', '投诉', '退', '失败', '被挤'].some((item) =>
|
||||
text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'danger'
|
||||
if (
|
||||
['未安排', '未', '暂缓', '暂停', '稍后', '延后', '挂起', '待定'].some((item) =>
|
||||
text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'warning'
|
||||
if (['排队', '等待', '等买家', '等扫码', '等验证', '待扫'].some((item) => text.includes(item)))
|
||||
return 'purple'
|
||||
if (
|
||||
['打单', '做单', '进行', '处理', '上号', '练级', '正在', '开打', '肝'].some((item) =>
|
||||
text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'processing'
|
||||
if (
|
||||
['已安排', '安排', '已就绪', '就绪', '已联系', '完成', '已好', '搞定', '正常', 'ok'].some(
|
||||
(item) => text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'success'
|
||||
return 'default'
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
import {
|
||||
CopyOutlined,
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
MessageOutlined,
|
||||
PictureOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { Button, Space, Tag, Tooltip, Typography } from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
|
||||
import { DeadlineCountdown } from '@/components/DeadlineCountdown'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import type { WorkOrder } from '@/types/worker-platform'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
|
||||
import {
|
||||
canEditAcceptanceImages,
|
||||
canRemindAcceptance,
|
||||
canRequestCancel,
|
||||
canSubmitAcceptance,
|
||||
canSubmitFeedback,
|
||||
formatMoney,
|
||||
formatStatus,
|
||||
getAcceptanceSubmittedAt,
|
||||
getCopyOrderInfoItems,
|
||||
getDisplayOrderNo,
|
||||
getMaterialDetailItems,
|
||||
getMaterialScreenshots,
|
||||
getUniqueAcceptanceFiles,
|
||||
getWorkerAcceptance,
|
||||
getWorkerDraft,
|
||||
hasSubmittedAcceptance,
|
||||
isVipEvidencePending,
|
||||
resolveStatusColor,
|
||||
} from './worker-order-utils'
|
||||
|
||||
type NoteTheme = 'success' | 'processing' | 'danger' | 'warning' | 'purple' | 'default'
|
||||
|
||||
export function buildWorkerOrderTableColumns({
|
||||
onOpenDetail,
|
||||
onOpenAcceptance,
|
||||
onRemindAcceptance,
|
||||
onOpenCancelRequest,
|
||||
onOpenFeedback,
|
||||
onOpenNote,
|
||||
onCopyOrderInfo,
|
||||
onCopyMaterialImages,
|
||||
}: {
|
||||
onOpenDetail: (order: WorkOrder) => void
|
||||
onOpenAcceptance: (order: WorkOrder) => void
|
||||
onRemindAcceptance: (order: WorkOrder) => void
|
||||
onOpenCancelRequest: (order: WorkOrder) => void
|
||||
onOpenFeedback: (order: WorkOrder) => void
|
||||
onOpenNote: (order: WorkOrder) => void
|
||||
onCopyOrderInfo: (order: WorkOrder) => void
|
||||
onCopyMaterialImages: (order: WorkOrder) => void
|
||||
}): TableColumnsType<WorkOrder> {
|
||||
return [
|
||||
{
|
||||
title: '订单信息',
|
||||
width: 270,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Space size={4} align="center">
|
||||
{row.isUnread ? (
|
||||
<Tag color="purple" className="worker-order-unread-tag">
|
||||
新
|
||||
</Tag>
|
||||
) : null}
|
||||
<Typography.Link
|
||||
strong
|
||||
onClick={() => onOpenDetail(row)}
|
||||
title={row.productName || '未命名商品'}
|
||||
ellipsis
|
||||
>
|
||||
{row.productName || '未命名商品'}
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
<Space size={[4, 4]} wrap>
|
||||
{row.categoryName ? <Tag color="blue">{row.categoryName}</Tag> : null}
|
||||
{row.myShare ? <Tag color="purple">拼单</Tag> : null}
|
||||
</Space>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ fontSize: 12 }}
|
||||
ellipsis={{ tooltip: getDisplayOrderNo(row) }}
|
||||
>
|
||||
{getDisplayOrderNo(row)}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
接单:{formatDateTime(row.assignedAt)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 130,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Space wrap size={4}>
|
||||
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
|
||||
{row.status === 'in_progress' && row.deadlineAt ? (
|
||||
<DeadlineCountdown deadlineAt={row.deadlineAt} />
|
||||
) : null}
|
||||
</Space>
|
||||
{row.status === 'problem' && String(row.problemNote || '').trim() ? (
|
||||
<Typography.Text
|
||||
type="danger"
|
||||
ellipsis={{ tooltip: row.problemNote }}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{row.problemNote}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '奖励 / 押金',
|
||||
width: 140,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text strong style={{ color: '#cf1322' }}>
|
||||
{formatMoney(row.rewardAmount)}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
押金:{formatMoney(row.freezeDepositAmount)}
|
||||
</Typography.Text>
|
||||
{row.myShare ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{row.myShare.quantity} 份 · {formatMoney(row.myShare.shareReward)}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '验收信息',
|
||||
width: 220,
|
||||
render: (_, row) => <AcceptanceCell order={row} />,
|
||||
},
|
||||
{
|
||||
title: '快捷复制',
|
||||
width: 250,
|
||||
render: (_, row) => {
|
||||
const items = getMaterialDetailItems(row).filter((item) => String(item.value || '').trim())
|
||||
const screenshots = getMaterialScreenshots(row)
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
{items.length > 0 ? (
|
||||
items.map((item) => (
|
||||
<Typography.Text
|
||||
key={item.key}
|
||||
style={{ fontSize: 12 }}
|
||||
ellipsis={{ tooltip: `${item.label}: ${item.value}` }}
|
||||
>
|
||||
{item.label}: {item.value}
|
||||
</Typography.Text>
|
||||
))
|
||||
) : (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
暂无资料信息
|
||||
</Typography.Text>
|
||||
)}
|
||||
{screenshots.length > 0 ? <ImagePreviewList files={screenshots} size={34} /> : null}
|
||||
<Space wrap size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
disabled={getCopyOrderInfoItems(row).length === 0}
|
||||
onClick={() => onCopyOrderInfo(row)}
|
||||
>
|
||||
复制信息
|
||||
</Button>
|
||||
{screenshots.length > 0 ? (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<PictureOutlined />}
|
||||
onClick={() => onCopyMaterialImages(row)}
|
||||
>
|
||||
复制图片
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
width: 180,
|
||||
render: (_, row) => <NoteCell order={row} onOpenNote={onOpenNote} />,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 150,
|
||||
render: (_, row) => (
|
||||
<div className="worker-order-actions">
|
||||
<Space size={6}>
|
||||
<Tooltip title="查看详情">
|
||||
<Button
|
||||
aria-label="查看详情"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => onOpenDetail(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
{canEditAcceptanceImages(row) ? (
|
||||
<Button
|
||||
icon={<PictureOutlined />}
|
||||
type={canSubmitAcceptance(row) ? 'primary' : 'default'}
|
||||
onClick={() => onOpenAcceptance(row)}
|
||||
>
|
||||
{hasSubmittedAcceptance(row) ? '补充图片' : '验收'}
|
||||
</Button>
|
||||
) : null}
|
||||
{canRemindAcceptance(row) ? (
|
||||
<Button onClick={() => onRemindAcceptance(row)}>催验收</Button>
|
||||
) : null}
|
||||
{canRequestCancel(row) ? (
|
||||
<Button danger onClick={() => onOpenCancelRequest(row)}>
|
||||
申请取消
|
||||
</Button>
|
||||
) : null}
|
||||
{canSubmitFeedback(row) ? (
|
||||
<Tooltip title="反馈客户资料或订单处理问题">
|
||||
<Button icon={<MessageOutlined />} onClick={() => onOpenFeedback(row)}>
|
||||
反馈
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function AcceptanceCell({ order }: { order: WorkOrder }) {
|
||||
const submittedAt = getAcceptanceSubmittedAt(order)
|
||||
const acceptanceFiles = getUniqueAcceptanceFiles(getWorkerAcceptance(order))
|
||||
const draftFiles = getUniqueAcceptanceFiles(getWorkerDraft(order))
|
||||
if (acceptanceFiles.length === 0 && !submittedAt && draftFiles.length === 0) {
|
||||
return (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
未提交
|
||||
</Typography.Text>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
{acceptanceFiles.length > 0 ? (
|
||||
<ImagePreviewList files={acceptanceFiles} size={38} maxVisible={2} />
|
||||
) : null}
|
||||
{submittedAt ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
提交:{formatDateTime(submittedAt)}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{isVipEvidencePending(order) ? <Tag color="purple">VIP 免审核,待补图</Tag> : null}
|
||||
{draftFiles.length > 0 && !hasSubmittedAcceptance(order) ? (
|
||||
<div className="acceptance-draft-badge-wrap">
|
||||
<Tag color="processing" icon={<PictureOutlined />}>
|
||||
已暂存 {draftFiles.length} 张图片
|
||||
</Tag>
|
||||
<ImagePreviewList files={draftFiles} size={38} maxVisible={2} />
|
||||
</div>
|
||||
) : null}
|
||||
{getWorkerAcceptance(order)?.note ? (
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
ellipsis={{ tooltip: getWorkerAcceptance(order)?.note }}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{getWorkerAcceptance(order)?.note}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NoteCell({
|
||||
order,
|
||||
onOpenNote,
|
||||
}: {
|
||||
order: WorkOrder
|
||||
onOpenNote: (order: WorkOrder) => void
|
||||
}) {
|
||||
const note = String(order.workerNote || '').trim()
|
||||
return (
|
||||
<div className="worker-note-cell">
|
||||
{note ? (
|
||||
<div
|
||||
className={`worker-note-badge theme-${resolveNoteTheme(note)}`}
|
||||
onClick={() => onOpenNote(order)}
|
||||
>
|
||||
<span className="worker-note-text" title={note}>
|
||||
{note}
|
||||
</span>
|
||||
<EditOutlined className="worker-note-edit-icon" />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
className="worker-note-add-btn"
|
||||
onClick={() => onOpenNote(order)}
|
||||
>
|
||||
添加备注
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function resolveNoteTheme(note?: string): NoteTheme {
|
||||
const text = String(note || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (!text) return 'default'
|
||||
if (
|
||||
['问题', '异常', '密码', '封号', '错误', '无法', '投诉', '退', '失败', '被挤'].some((item) =>
|
||||
text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'danger'
|
||||
if (
|
||||
['未安排', '未', '暂缓', '暂停', '稍后', '延后', '挂起', '待定'].some((item) =>
|
||||
text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'warning'
|
||||
if (['排队', '等待', '等买家', '等扫码', '等验证', '待扫'].some((item) => text.includes(item)))
|
||||
return 'purple'
|
||||
if (
|
||||
['打单', '做单', '进行', '处理', '上号', '练级', '正在', '开打', '肝'].some((item) =>
|
||||
text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'processing'
|
||||
if (
|
||||
['已安排', '安排', '已就绪', '就绪', '已联系', '完成', '已好', '搞定', '正常', 'ok'].some(
|
||||
(item) => text.includes(item),
|
||||
)
|
||||
)
|
||||
return 'success'
|
||||
return 'default'
|
||||
}
|
||||
@@ -1,31 +1,7 @@
|
||||
import {
|
||||
CopyOutlined,
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
MessageOutlined,
|
||||
PictureOutlined,
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
Pagination,
|
||||
Space,
|
||||
Spin,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import { App, Form } from 'antd'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { DeadlineCountdown } from '@/components/DeadlineCountdown'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import {
|
||||
fetchWorkerCancelRequests,
|
||||
fetchWorkerOrderFeedbacks,
|
||||
@@ -42,12 +18,12 @@ import {
|
||||
submitWorkerOrderFeedback,
|
||||
} from '@/services/worker'
|
||||
import type { UploadedFile, WorkOrder } from '@/types/worker-platform'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
import { useIsMobile } from '@/utils/use-is-mobile'
|
||||
import { buildWorkerOrderQuery } from '@/utils/worker-order-query'
|
||||
import WorkerAfterSalesSection from './WorkerAfterSalesSection'
|
||||
import { WorkerOrderDesktopList } from './WorkerOrderDesktopList'
|
||||
import { WorkerOrderDetailDrawer } from './WorkerOrderDetailDrawer'
|
||||
import { WorkerOrderMobileList } from './WorkerOrderMobileList'
|
||||
import {
|
||||
type AcceptanceFormValues,
|
||||
type CancelRequestFormValues,
|
||||
@@ -55,30 +31,17 @@ import {
|
||||
type WorkerNoteFormValues,
|
||||
WorkerOrderModals,
|
||||
} from './WorkerOrderModals'
|
||||
import { buildWorkerOrderTableColumns } from './WorkerOrderTableColumns'
|
||||
import {
|
||||
asRecord,
|
||||
canAutoAcceptOrder,
|
||||
canEditAcceptanceImages,
|
||||
canRemindAcceptance,
|
||||
canRequestCancel,
|
||||
canSubmitAcceptance,
|
||||
canSubmitFeedback,
|
||||
canSupplementAcceptedEvidence,
|
||||
formatMoney,
|
||||
formatStatus,
|
||||
getAcceptanceSubmittedAt,
|
||||
getCopyOrderInfoItems,
|
||||
getDisplayOrderNo,
|
||||
getMaterialDetailItems,
|
||||
getMaterialScreenshots,
|
||||
getSettlementLabel,
|
||||
getUniqueAcceptanceFiles,
|
||||
getWorkerAcceptance,
|
||||
getWorkerDraft,
|
||||
hasSubmittedAcceptance,
|
||||
isVipEvidencePending,
|
||||
patchWorkOrderReadState,
|
||||
resolveStatusColor,
|
||||
toPngBlob,
|
||||
} from './worker-order-utils'
|
||||
|
||||
@@ -461,598 +424,71 @@ export default function WorkerOrdersPage() {
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const columns: TableColumnsType<WorkOrder> = [
|
||||
{
|
||||
title: '订单信息',
|
||||
width: 270,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Space size={4} align="center">
|
||||
{row.isUnread ? (
|
||||
<Tag color="purple" className="worker-order-unread-tag">
|
||||
新
|
||||
</Tag>
|
||||
) : null}
|
||||
<Typography.Link
|
||||
strong
|
||||
onClick={() => openOrderDetail(row)}
|
||||
title={row.productName || '未命名商品'}
|
||||
ellipsis
|
||||
>
|
||||
{row.productName || '未命名商品'}
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
<Space size={[4, 4]} wrap>
|
||||
{row.categoryName ? <Tag color="blue">{row.categoryName}</Tag> : null}
|
||||
{row.myShare ? <Tag color="purple">拼单</Tag> : null}
|
||||
</Space>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ fontSize: 12 }}
|
||||
ellipsis={{ tooltip: getDisplayOrderNo(row) }}
|
||||
>
|
||||
{getDisplayOrderNo(row)}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
接单:{formatDateTime(row.assignedAt)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
const columns = buildWorkerOrderTableColumns({
|
||||
onOpenDetail: openOrderDetail,
|
||||
onOpenAcceptance: openAcceptanceModal,
|
||||
onRemindAcceptance: (order) => {
|
||||
void submitAcceptanceReminder(order)
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 130,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Space wrap size={4}>
|
||||
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
|
||||
{row.status === 'in_progress' && row.deadlineAt ? (
|
||||
<DeadlineCountdown deadlineAt={row.deadlineAt} />
|
||||
) : null}
|
||||
</Space>
|
||||
{row.status === 'problem' && String(row.problemNote || '').trim() ? (
|
||||
<Typography.Text
|
||||
type="danger"
|
||||
ellipsis={{ tooltip: row.problemNote }}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{row.problemNote}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
onOpenCancelRequest: openCancelRequestModal,
|
||||
onOpenFeedback: openFeedbackModal,
|
||||
onOpenNote: openNoteModal,
|
||||
onCopyOrderInfo: copyOrderInfo,
|
||||
onCopyMaterialImages: (order) => {
|
||||
void copyMaterialImages(order)
|
||||
},
|
||||
{
|
||||
title: '奖励 / 押金',
|
||||
width: 140,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text strong style={{ color: '#cf1322' }}>
|
||||
{formatMoney(row.rewardAmount)}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
押金:{formatMoney(row.freezeDepositAmount)}
|
||||
</Typography.Text>
|
||||
{row.myShare ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{row.myShare.quantity} 份 · {formatMoney(row.myShare.shareReward)}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '验收信息',
|
||||
width: 220,
|
||||
render: (_, row) => {
|
||||
const submittedAt = getAcceptanceSubmittedAt(row)
|
||||
const acceptanceFiles = getUniqueAcceptanceFiles(getWorkerAcceptance(row))
|
||||
const hasAcceptance = acceptanceFiles.length > 0
|
||||
const draftFiles = getUniqueAcceptanceFiles(getWorkerDraft(row))
|
||||
const draftCount = draftFiles.length
|
||||
const vipEvidencePending = isVipEvidencePending(row)
|
||||
|
||||
if (!hasAcceptance && !submittedAt && draftCount === 0) {
|
||||
return (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
未提交
|
||||
</Typography.Text>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
{hasAcceptance ? (
|
||||
<ImagePreviewList files={acceptanceFiles} size={38} maxVisible={2} />
|
||||
) : null}
|
||||
{submittedAt ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
提交:{formatDateTime(submittedAt)}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{vipEvidencePending ? <Tag color="purple">VIP 免审核,待补图</Tag> : null}
|
||||
{draftCount > 0 && !hasSubmittedAcceptance(row) ? (
|
||||
<div className="acceptance-draft-badge-wrap">
|
||||
<Tag color="processing" icon={<PictureOutlined />}>
|
||||
已暂存 {draftCount} 张图片
|
||||
</Tag>
|
||||
<ImagePreviewList files={draftFiles} size={38} maxVisible={2} />
|
||||
</div>
|
||||
) : null}
|
||||
{getWorkerAcceptance(row)?.note ? (
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
ellipsis={{ tooltip: getWorkerAcceptance(row)?.note }}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{getWorkerAcceptance(row)?.note}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '快捷复制',
|
||||
width: 250,
|
||||
render: (_, row) => {
|
||||
const items = getMaterialDetailItems(row).filter((item) => String(item.value || '').trim())
|
||||
const canCopyInfo = getCopyOrderInfoItems(row).length > 0
|
||||
const screenshots = getMaterialScreenshots(row)
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
{items.length > 0 ? (
|
||||
items.map((item) => (
|
||||
<Typography.Text
|
||||
key={item.key}
|
||||
style={{ fontSize: 12 }}
|
||||
ellipsis={{ tooltip: `${item.label}: ${item.value}` }}
|
||||
>
|
||||
{item.label}: {item.value}
|
||||
</Typography.Text>
|
||||
))
|
||||
) : (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
暂无资料信息
|
||||
</Typography.Text>
|
||||
)}
|
||||
{screenshots.length > 0 ? <ImagePreviewList files={screenshots} size={34} /> : null}
|
||||
<Space wrap size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
disabled={!canCopyInfo}
|
||||
onClick={() => copyOrderInfo(row)}
|
||||
>
|
||||
复制信息
|
||||
</Button>
|
||||
{screenshots.length > 0 ? (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<PictureOutlined />}
|
||||
onClick={() => copyMaterialImages(row)}
|
||||
>
|
||||
复制图片
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
width: 180,
|
||||
render: (_, row) => {
|
||||
const note = String(row.workerNote || '').trim()
|
||||
const theme = resolveNoteTheme(note)
|
||||
return (
|
||||
<div className="worker-note-cell">
|
||||
{note ? (
|
||||
<div
|
||||
className={`worker-note-badge theme-${theme}`}
|
||||
onClick={() => openNoteModal(row)}
|
||||
>
|
||||
<span className="worker-note-text" title={note}>
|
||||
{note}
|
||||
</span>
|
||||
<EditOutlined className="worker-note-edit-icon" />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
className="worker-note-add-btn"
|
||||
onClick={() => openNoteModal(row)}
|
||||
>
|
||||
添加备注
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 150,
|
||||
render: (_, row) => (
|
||||
<div className="worker-order-actions">
|
||||
<Space size={6}>
|
||||
<Tooltip title="查看详情">
|
||||
<Button
|
||||
aria-label="查看详情"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => openOrderDetail(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
{canEditAcceptanceImages(row) ? (
|
||||
<Button
|
||||
icon={<PictureOutlined />}
|
||||
type={canSubmitAcceptance(row) ? 'primary' : 'default'}
|
||||
onClick={() => openAcceptanceModal(row)}
|
||||
>
|
||||
{hasSubmittedAcceptance(row) ? '补充图片' : '验收'}
|
||||
</Button>
|
||||
) : null}
|
||||
{canRemindAcceptance(row) ? (
|
||||
<Button onClick={() => submitAcceptanceReminder(row)}>催验收</Button>
|
||||
) : null}
|
||||
{canRequestCancel(row) ? (
|
||||
<Button danger onClick={() => openCancelRequestModal(row)}>
|
||||
申请取消
|
||||
</Button>
|
||||
) : null}
|
||||
{canSubmitFeedback(row) ? (
|
||||
<Tooltip title="反馈客户资料或订单处理问题">
|
||||
<Button icon={<MessageOutlined />} onClick={() => openFeedbackModal(row)}>
|
||||
反馈
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkerAfterSalesSection isMobile={isMobile} />
|
||||
{isMobile ? (
|
||||
<div className="worker-orders-mobile-page">
|
||||
{/* 顶部水平滑动状态切换胶囊条 */}
|
||||
<div className="worker-orders-mobile-status-bar">
|
||||
{STATUS_OPTIONS.map((item) => {
|
||||
const countKey = item.value || 'all'
|
||||
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'}
|
||||
type="button"
|
||||
className={`worker-orders-status-pill ${isActive ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setStatus(item.value)
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 搜索与刷新栏 */}
|
||||
<div className="worker-orders-mobile-search-row">
|
||||
<Input
|
||||
allowClear
|
||||
value={keywordInput}
|
||||
placeholder="搜索订单号 / 商品名 / 游戏ID / 游戏昵称"
|
||||
prefix={<SearchOutlined />}
|
||||
className="worker-orders-mobile-search-input"
|
||||
onChange={(event) => {
|
||||
const nextValue = event.target.value
|
||||
setKeywordInput(nextValue)
|
||||
if (!nextValue.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onPressEnter={applyKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
loading={ordersQuery.isFetching}
|
||||
onClick={applyKeywordSearch}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={ordersQuery.isFetching || overviewQuery.isFetching}
|
||||
onClick={() => refreshAll()}
|
||||
title="刷新"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 移动端紧凑卡片列表 */}
|
||||
<div className="worker-orders-mobile-list">
|
||||
{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}” 相关的订单` : '当前筛选下暂无订单'}
|
||||
/>
|
||||
) : (
|
||||
orders.map((order) => {
|
||||
const items = getMaterialDetailItems(order).filter((item) =>
|
||||
String(item.value || '').trim(),
|
||||
)
|
||||
const screenshots = getMaterialScreenshots(order)
|
||||
const canCopyInfo = getCopyOrderInfoItems(order).length > 0
|
||||
const _submittedAt = getAcceptanceSubmittedAt(order)
|
||||
const acceptanceFiles = getUniqueAcceptanceFiles(getWorkerAcceptance(order))
|
||||
const hasAcceptance = acceptanceFiles.length > 0
|
||||
const draftFiles = getUniqueAcceptanceFiles(getWorkerDraft(order))
|
||||
const draftCount = draftFiles.length
|
||||
const noteText = getWorkerAcceptance(order)?.note
|
||||
const workerNote = String(order.workerNote || '').trim()
|
||||
|
||||
return (
|
||||
<article key={order.workOrderId} className="worker-order-mobile-card">
|
||||
{/* 卡片顶栏:分类 + 订单号 + 状态/倒计时 */}
|
||||
<div className="worker-order-mobile-header">
|
||||
<div className="worker-order-mobile-header-left">
|
||||
{order.isUnread ? (
|
||||
<Tag color="purple" className="worker-order-unread-tag">
|
||||
新
|
||||
</Tag>
|
||||
) : null}
|
||||
{order.categoryName ? (
|
||||
<Tag color="blue" className="worker-order-mobile-tag">
|
||||
{order.categoryName}
|
||||
</Tag>
|
||||
) : null}
|
||||
{order.myShare ? (
|
||||
<Tag color="purple" className="worker-order-mobile-tag">
|
||||
拼单
|
||||
</Tag>
|
||||
) : null}
|
||||
<span className="worker-order-mobile-orderno-text">
|
||||
{getDisplayOrderNo(order)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="worker-order-mobile-header-right">
|
||||
<Tag color={resolveStatusColor(order.status)}>
|
||||
{formatStatus(order.status)}
|
||||
</Tag>
|
||||
{order.status === 'in_progress' && order.deadlineAt ? (
|
||||
<DeadlineCountdown deadlineAt={order.deadlineAt} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 商品标题与金额 */}
|
||||
<div className="worker-order-mobile-main-row">
|
||||
<div className="worker-order-mobile-product-title">
|
||||
{order.productName || '未命名商品'}
|
||||
</div>
|
||||
<div className="worker-order-mobile-price-badge">
|
||||
<span className="worker-order-mobile-price-label">报酬</span>
|
||||
<strong className="worker-order-mobile-price-num">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 辅助金额与接单时间 */}
|
||||
<div className="worker-order-mobile-sub-row">
|
||||
<span className="worker-order-mobile-sub-item">
|
||||
押金: {formatMoney(order.freezeDepositAmount)}
|
||||
</span>
|
||||
{order.myShare ? (
|
||||
<span className="worker-order-mobile-sub-item">
|
||||
已拼 {order.myShare.quantity} 份 ({formatMoney(order.myShare.shareReward)}
|
||||
)
|
||||
</span>
|
||||
) : null}
|
||||
<span className="worker-order-mobile-sub-time">
|
||||
{formatDateTime(order.assignedAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 问题单提示 */}
|
||||
{order.status === 'problem' && String(order.problemNote || '').trim() ? (
|
||||
<div className="worker-order-mobile-problem-box">
|
||||
<strong>问题:</strong>
|
||||
<span>{order.problemNote}</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* 买家资料与截图 */}
|
||||
{(items.length > 0 || screenshots.length > 0) && (
|
||||
<div className="worker-order-mobile-credentials-box">
|
||||
{items.length > 0 ? (
|
||||
<div className="worker-order-mobile-credentials-grid">
|
||||
{items.map((item) => (
|
||||
<div key={item.key} className="worker-order-mobile-credential-line">
|
||||
<span className="worker-order-mobile-credential-label">
|
||||
{item.label}:
|
||||
</span>
|
||||
<span className="worker-order-mobile-credential-val">
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{screenshots.length > 0 ? (
|
||||
<div className="worker-order-mobile-screenshots-row">
|
||||
<span className="worker-order-mobile-credential-label">截图:</span>
|
||||
<ImagePreviewList files={screenshots} size={34} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="worker-order-mobile-copy-toolbar">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
disabled={!canCopyInfo}
|
||||
onClick={() => copyOrderInfo(order)}
|
||||
>
|
||||
复制资料
|
||||
</Button>
|
||||
{screenshots.length > 0 ? (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<PictureOutlined />}
|
||||
onClick={() => copyMaterialImages(order)}
|
||||
>
|
||||
复制图片
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 验收凭据与暂存状态 */}
|
||||
{(hasAcceptance ||
|
||||
(draftCount > 0 && !hasSubmittedAcceptance(order)) ||
|
||||
noteText) && (
|
||||
<div className="worker-order-mobile-acceptance-strip">
|
||||
{hasAcceptance ? (
|
||||
<div className="worker-order-mobile-acceptance-files">
|
||||
<span className="worker-order-mobile-acceptance-tag">
|
||||
已提交凭据 ({acceptanceFiles.length}张)
|
||||
</span>
|
||||
<ImagePreviewList files={acceptanceFiles} size={32} maxVisible={3} />
|
||||
</div>
|
||||
) : null}
|
||||
{isVipEvidencePending(order) ? (
|
||||
<Tag color="purple">VIP 免审核,待补图</Tag>
|
||||
) : null}
|
||||
{draftCount > 0 && !hasSubmittedAcceptance(order) ? (
|
||||
<div className="worker-order-mobile-draft-files">
|
||||
<Tag color="processing" icon={<PictureOutlined />}>
|
||||
已暂存 {draftCount} 张图片
|
||||
</Tag>
|
||||
<ImagePreviewList files={draftFiles} size={32} maxVisible={3} />
|
||||
</div>
|
||||
) : null}
|
||||
{noteText ? (
|
||||
<div className="worker-order-mobile-acceptance-text">
|
||||
说明:{noteText}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 底部备注与操作 */}
|
||||
<div className="worker-order-mobile-footer-row">
|
||||
<div className="worker-order-mobile-note-block">
|
||||
{workerNote ? (
|
||||
<div
|
||||
className={`worker-note-badge theme-${resolveNoteTheme(workerNote)}`}
|
||||
onClick={() => openNoteModal(order)}
|
||||
>
|
||||
<span className="worker-note-text" title={workerNote}>
|
||||
{workerNote}
|
||||
</span>
|
||||
<EditOutlined className="worker-note-edit-icon" />
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="worker-order-mobile-add-note-btn"
|
||||
onClick={() => openNoteModal(order)}
|
||||
>
|
||||
<EditOutlined /> 添加备注
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="worker-order-mobile-btn-group">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => openOrderDetail(order)}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
{canEditAcceptanceImages(order) ? (
|
||||
<Button
|
||||
size="small"
|
||||
type={canSubmitAcceptance(order) ? 'primary' : 'default'}
|
||||
icon={<PictureOutlined />}
|
||||
className="worker-order-mobile-action-submit"
|
||||
onClick={() => openAcceptanceModal(order)}
|
||||
>
|
||||
{hasSubmittedAcceptance(order) ? '补充图片' : '提交验收'}
|
||||
</Button>
|
||||
) : null}
|
||||
{canRemindAcceptance(order) ? (
|
||||
<Button size="small" onClick={() => submitAcceptanceReminder(order)}>
|
||||
催验收
|
||||
</Button>
|
||||
) : null}
|
||||
{canRequestCancel(order) ? (
|
||||
<Button size="small" danger onClick={() => openCancelRequestModal(order)}>
|
||||
申请取消
|
||||
</Button>
|
||||
) : null}
|
||||
{canSubmitFeedback(order) ? (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<MessageOutlined />}
|
||||
onClick={() => openFeedbackModal(order)}
|
||||
>
|
||||
反馈
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})
|
||||
)}
|
||||
|
||||
{(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}
|
||||
</div>
|
||||
</div>
|
||||
<WorkerOrderMobileList
|
||||
statusOptions={STATUS_OPTIONS}
|
||||
status={status}
|
||||
overviewByStatus={overviewByStatus}
|
||||
keywordInput={keywordInput}
|
||||
keyword={keyword}
|
||||
orders={orders}
|
||||
pagination={pagination}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
loading={ordersQuery.isLoading}
|
||||
searching={ordersQuery.isFetching}
|
||||
refreshing={ordersQuery.isFetching || overviewQuery.isFetching}
|
||||
error={ordersQuery.error}
|
||||
onStatusChange={(nextStatus) => {
|
||||
setStatus(nextStatus)
|
||||
setPage(1)
|
||||
}}
|
||||
onKeywordInputChange={(nextValue) => {
|
||||
setKeywordInput(nextValue)
|
||||
if (!nextValue.trim()) {
|
||||
setKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
onKeywordSearch={applyKeywordSearch}
|
||||
onRefresh={() => {
|
||||
void refreshAll()
|
||||
}}
|
||||
onPageChange={(nextPage, nextPageSize) => {
|
||||
setPage(nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
onOpenDetail={openOrderDetail}
|
||||
onOpenAcceptance={openAcceptanceModal}
|
||||
onRemindAcceptance={(order) => {
|
||||
void submitAcceptanceReminder(order)
|
||||
}}
|
||||
onOpenCancelRequest={openCancelRequestModal}
|
||||
onOpenFeedback={openFeedbackModal}
|
||||
onOpenNote={openNoteModal}
|
||||
onCopyOrderInfo={copyOrderInfo}
|
||||
onCopyMaterialImages={(order) => {
|
||||
void copyMaterialImages(order)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<WorkerOrderDesktopList
|
||||
statusOptions={STATUS_OPTIONS}
|
||||
@@ -1062,8 +498,11 @@ export default function WorkerOrdersPage() {
|
||||
keyword={keyword}
|
||||
orders={orders}
|
||||
pagination={pagination}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
columns={columns}
|
||||
loading={ordersQuery.isLoading}
|
||||
searching={ordersQuery.isFetching}
|
||||
refreshing={ordersQuery.isFetching || overviewQuery.isFetching}
|
||||
error={ordersQuery.error}
|
||||
onRefresh={() => {
|
||||
|
||||
Reference in New Issue
Block a user