add:新增同一个工单同一人可以多次拼单

This commit is contained in:
2026-10-23 14:26:41 +08:00
parent 72a758fd7c
commit bfd2325ac5
28 changed files with 1332 additions and 347 deletions
@@ -23,6 +23,11 @@ const notificationEventRows: NotificationEventRow[] = [
{ key: 'recharge_requested', name: '充值申请', description: '打手提交新的充值申请' },
{ key: 'acceptance_submitted', name: '提交验收', description: '打手提交订单验收资料' },
{ key: 'acceptance_reminded', name: '催验收', description: '打手在验收后催促处理' },
{
key: 'order_accepted',
name: '整单验收完成',
description: '全部份额验收完成(含拼单统一验收)',
},
{ key: 'material_required', name: '待补资料工单', description: '新工单等待后台补充资料' },
{
key: 'cancel_requested',
@@ -114,6 +114,7 @@ import {
asRecord,
formatMoney,
formatSharingShareStatus,
formatSharingShareCancelledReason,
formatStatus,
resolveGiftPhaseColor,
resolveSharingShareStatusColor,
@@ -3145,6 +3146,7 @@ export default function WorkOrdersPanel() {
dataSource={sharingShares}
loading={sharingQuery.isFetching}
pagination={false}
scroll={{ x: 1280 }}
columns={[
{
title: '打手',
@@ -3159,11 +3161,38 @@ export default function WorkOrdersPanel() {
{
title: '状态',
render: (_, share) => (
<Tag color={resolveSharingShareStatusColor(share.status)}>
{formatSharingShareStatus(share.status)}
</Tag>
<Space size={4} wrap>
<Tag color={resolveSharingShareStatusColor(share.status)}>
{formatSharingShareStatus(share.status)}
</Tag>
{share.status === 'cancelled' && share.cancelledReason ? (
<Tag>{formatSharingShareCancelledReason(share.cancelledReason)}</Tag>
) : null}
</Space>
),
},
{
title: '参与时间',
render: (_, share) =>
share.createdAt ? formatAdminDateTime(share.createdAt) : '-',
},
{
title: '截止时间',
render: (_, share) =>
share.deadlineAt ? (
<span>
{formatAdminDateTime(share.deadlineAt)}
{share.timeoutMinutes ? ` · ${share.timeoutMinutes} 分钟` : null}
</span>
) : (
<Typography.Text type="secondary">不限时</Typography.Text>
),
},
{
title: '超时策略',
render: (_, share) =>
share.timeoutPolicy ? formatTimeoutPolicyLabel(share.timeoutPolicy) : '-',
},
{
title: '提交时间',
render: (_, share) =>
@@ -252,6 +252,16 @@ export function formatSharingShareStatus(status: string) {
return labels[status] || status
}
/** 撤单原因:份额级独立撤单后,后台需要一眼看出是谁撤的。 */
export function formatSharingShareCancelledReason(reason: string) {
const labels: Record<string, string> = {
timeout: '超时失效',
admin: '后台撤单',
worker_request: '打手退单',
}
return labels[reason] || reason
}
export function formatAcceptanceMode(mode: string) {
if (mode === 'friend_gift') return '好友赠送'
return '标准验收'
@@ -66,6 +66,7 @@ import {
resolveHallRewardAmount,
} from '@/utils/worker-product-name'
import { formatDateTime, formatPublishedElapsedTime } from '@/utils/date-time'
import { createRequestId } from '@/utils/request-id'
type HallSummary = {
pendingAcceptanceCount: number
@@ -314,7 +315,7 @@ export default function WorkerHallPage() {
async function submitGrab(order: WorkOrder) {
setGrabbingId(order.workOrderId)
try {
await grabWorkerOrder(order.workOrderId)
await grabWorkerOrder(order.workOrderId, { requestId: createRequestId('grab') })
message.success('抢单成功,已进入我的订单')
await refreshAll()
} catch (error) {
@@ -335,6 +336,7 @@ export default function WorkerHallPage() {
try {
await joinWorkerSharingOrder(sharingOrder.workOrderId, {
quantity: sharingQuantity,
requestId: createRequestId('share'),
})
message.success('拼单成功,已进入我的订单')
setSharingOrder(null)
@@ -346,6 +348,15 @@ export default function WorkerHallPage() {
}
}
/** 我在该工单的份额摘要:参与次数 + 累计份数 */
function resolveMyShareSummary(order: WorkOrder): { count: number; quantity: number } {
const shares = order.myShares?.length ? order.myShares : order.myShare ? [order.myShare] : []
return {
count: shares.length,
quantity: shares.reduce((total, share) => total + Number(share.quantity || 0), 0),
}
}
function resolveSharingRemaining(order: WorkOrder): number {
return resolveHallRemainingQuantity(order)
}
@@ -700,18 +711,18 @@ export default function WorkerHallPage() {
</div>
<div className="worker-hall-mobile-card-btn-group">
{order.sharing?.enabled &&
(sharingRemaining > 0 || order.myShare) &&
!disabledReason ? (
{order.sharing?.enabled && sharingRemaining > 0 && !disabledReason ? (
<Button
type="primary"
size="middle"
className="worker-hall-sharing-button"
disabled={Boolean(disabledReason) || Boolean(order.myShare)}
disabled={Boolean(disabledReason)}
onClick={() => openSharingModal(order)}
>
{order.myShare
? `已拼 ${order.myShare.quantity} 份`
{resolveMyShareSummary(order).count > 0
? `已参与 ${resolveMyShareSummary(order).count} 次 · 共 ${
resolveMyShareSummary(order).quantity
} 份 / 再拼`
: `拼单 ${formatMoney(order.sharing.unitReward)}/份`}
</Button>
) : null}
@@ -1152,18 +1163,18 @@ export default function WorkerHallPage() {
<div className="worker-hall-card-actions">
<div className="worker-hall-card-grab-row">
{order.sharing?.enabled &&
(sharingRemaining > 0 || order.myShare) &&
!disabledReason ? (
{order.sharing?.enabled && sharingRemaining > 0 && !disabledReason ? (
<Button
type="primary"
size="large"
className="worker-hall-sharing-button"
disabled={Boolean(disabledReason) || Boolean(order.myShare)}
disabled={Boolean(disabledReason)}
onClick={() => openSharingModal(order)}
>
{order.myShare
? `已拼 ${order.myShare.quantity} 份`
{resolveMyShareSummary(order).count > 0
? `已参与 ${resolveMyShareSummary(order).count} 次 · 共 ${
resolveMyShareSummary(order).quantity
} 份 / 再拼`
: `拼单 ${formatMoney(order.sharing.unitReward)}/份`}
</Button>
) : null}
@@ -8,6 +8,7 @@ import {
} from '@ant-design/icons'
import { Alert, Button, Card, Empty, Input, Space, Table, Tabs, Typography } from 'antd'
import type { TableColumnsType } from 'antd'
import type { ReactNode } from 'react'
import type { Pagination, WorkerOrderOverviewItem, WorkOrder } from '@/types/worker-platform'
@@ -29,6 +30,7 @@ export function WorkerOrderDesktopList({
page,
pageSize,
columns,
renderExpandedRow,
loading,
searching,
refreshing,
@@ -49,6 +51,7 @@ export function WorkerOrderDesktopList({
page: number
pageSize: number
columns: TableColumnsType<WorkOrder>
renderExpandedRow?: (order: WorkOrder) => ReactNode
loading: boolean
searching: boolean
refreshing: boolean
@@ -262,6 +265,15 @@ export function WorkerOrderDesktopList({
dataSource={orders}
columns={columns}
className="worker-orders-table"
expandable={
renderExpandedRow
? {
expandedRowRender: renderExpandedRow,
rowExpandable: (order) => (order.myShares?.length || 0) > 1,
columnWidth: 42,
}
: undefined
}
locale={{
emptyText: error ? (
<Empty description={error instanceof Error ? error.message : '读取我的订单失败'} />
@@ -7,7 +7,8 @@ import {
ReloadOutlined,
SearchOutlined,
} from '@ant-design/icons'
import { Alert, Button, Empty, Input, Pagination, Spin, Tag } from 'antd'
import { Alert, Button, Empty, Input, Pagination, Spin, Tag, Typography } from 'antd'
import type { ReactNode } from 'react'
import { DeadlineCountdown } from '@/components/DeadlineCountdown'
import ImagePreviewList from '@/components/files/ImagePreviewList'
@@ -53,6 +54,7 @@ export function WorkerOrderMobileList({
pagination,
page,
pageSize,
renderShareTable,
loading,
searching,
refreshing,
@@ -81,6 +83,7 @@ export function WorkerOrderMobileList({
pagination: PaginationData | undefined
page: number
pageSize: number
renderShareTable?: (order: WorkOrder) => ReactNode
loading: boolean
searching: boolean
refreshing: boolean
@@ -173,6 +176,7 @@ export function WorkerOrderMobileList({
<WorkerOrderMobileCard
key={order.workOrderId}
order={order}
renderShareTable={renderShareTable}
onOpenDetail={onOpenDetail}
onOpenAcceptance={onOpenAcceptance}
onOpenGiftMaterial={onOpenGiftMaterial}
@@ -203,6 +207,7 @@ export function WorkerOrderMobileList({
function WorkerOrderMobileCard({
order,
renderShareTable,
onOpenDetail,
onOpenAcceptance,
onOpenGiftMaterial,
@@ -326,14 +331,27 @@ function WorkerOrderMobileCard({
? '押金: 已解除冻结'
: `押金: ${formatMoney(order.freezeDepositAmount)}`}
</span>
{order.myShare ? (
{order.myShare || order.myShares?.length ? (
<span className="worker-order-mobile-sub-item">
已拼 {order.myShare.quantity} 份 ({formatMoney(order.myShare.shareReward)})
已参与 {order.myShares?.length || 1} 次 · 共{' '}
{order.myShares?.reduce((total, share) => total + share.quantity, 0) ||
order.myShare?.quantity ||
0}{' '}
份
</span>
) : null}
<span className="worker-order-mobile-sub-time">{formatDateTime(order.assignedAt)}</span>
</div>
{(order.myShares?.length || 0) > 1 && renderShareTable ? (
<div className="worker-order-mobile-shares">
<Typography.Text strong style={{ fontSize: 12 }}>
拼单明细({order.myShares?.length} 份)
</Typography.Text>
{renderShareTable(order)}
</div>
) : null}
{order.status === 'problem' && String(order.problemNote || '').trim() ? (
<div className="worker-order-mobile-problem-box">
<strong>问题:</strong>
@@ -83,7 +83,9 @@ export function buildWorkerOrderTableColumns({
</Space>
<Space size={[4, 4]} wrap>
{row.categoryName ? <Tag color="blue">{row.categoryName}</Tag> : null}
{row.myShare ? <Tag color="purple">拼单</Tag> : null}
{row.myShares?.length || row.myShare ? (
<Tag color="purple">拼单 {row.myShares?.length || 1} 次</Tag>
) : null}
</Space>
<Typography.Text
type="secondary"
@@ -165,9 +167,13 @@ export function buildWorkerOrderTableColumns({
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
押金:{formatMoney(row.freezeDepositAmount)}
</Typography.Text>
{row.myShare ? (
{row.myShare || row.myShares?.length ? (
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{row.myShare.quantity} 份 · {formatMoney(row.myShare.shareReward)}
{row.myShares?.length || 1} 次 ·{' '}
{row.myShares?.reduce((total, share) => total + share.quantity, 0) ||
row.myShare?.quantity ||
0}{' '}
份
</Typography.Text>
) : null}
</div>
@@ -239,48 +245,57 @@ export function buildWorkerOrderTableColumns({
{
title: '操作',
width: 150,
render: (_, row) => (
<div className="worker-order-actions">
<Space size={6}>
<Tooltip title="查看详情">
<Button
aria-label="查看详情"
icon={<EyeOutlined />}
onClick={() => onOpenDetail(row)}
/>
</Tooltip>
{canSubmitGiftMaterial(row) ? (
<Button type="primary" onClick={() => onOpenGiftMaterial(row)}>
提交资料
</Button>
) : null}
{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>
render: (_, row) => {
const shareCount = row.myShares?.length || (row.myShare ? 1 : 0)
const hasMultipleShares = shareCount > 1
return (
<div className="worker-order-actions">
<Space size={6} wrap>
<Tooltip title="查看详情">
<Button
aria-label="查看详情"
icon={<EyeOutlined />}
onClick={() => onOpenDetail(row)}
/>
</Tooltip>
) : null}
</Space>
</div>
),
{canSubmitGiftMaterial(row) ? (
<Button type="primary" onClick={() => onOpenGiftMaterial(row)}>
提交资料
</Button>
) : null}
{!hasMultipleShares && canEditAcceptanceImages(row) ? (
<Button
icon={<PictureOutlined />}
type={canSubmitAcceptance(row) ? 'primary' : 'default'}
onClick={() => onOpenAcceptance(row)}
>
{hasSubmittedAcceptance(row) ? '补充图片' : '验收'}
</Button>
) : null}
{!hasMultipleShares && canRemindAcceptance(row) ? (
<Button onClick={() => onRemindAcceptance(row)}>催验收</Button>
) : null}
{!hasMultipleShares && canRequestCancel(row) ? (
<Button danger onClick={() => onOpenCancelRequest(row)}>
申请取消
</Button>
) : null}
{hasMultipleShares ? (
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
展开行查看 {shareCount} 份份额操作
</Typography.Text>
) : null}
{canSubmitFeedback(row) ? (
<Tooltip title="反馈客户资料或订单处理问题">
<Button icon={<MessageOutlined />} onClick={() => onOpenFeedback(row)}>
反馈
</Button>
</Tooltip>
) : null}
</Space>
</div>
)
},
},
]
}
@@ -1,6 +1,6 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from 'react-router'
import { App, Badge, Button, Form, Space, Typography } from 'antd'
import { App, Badge, Button, Form, Modal, Radio, Space, Tag, Typography } from 'antd'
import { useState } from 'react'
import {
@@ -22,11 +22,13 @@ import {
} from '@/services/worker'
import type { UploadedFile, WorkOrder } from '@/types/worker-platform'
import { isExplicitOrderNoSearch, normalizeSearchText } from '@/utils/admin-realtime-work-orders'
import { formatDateTime } from '@/utils/date-time'
import { useIsMobile } from '@/utils/use-is-mobile'
import { buildWorkerOrderQuery } from '@/utils/worker-order-query'
import { WorkerOrderDesktopList } from './WorkerOrderDesktopList'
import { WorkerOrderDetailDrawer } from './WorkerOrderDetailDrawer'
import { WorkerOrderMobileList } from './WorkerOrderMobileList'
import { WorkerOrderShareTable } from './WorkerOrderShareTable'
import {
type AcceptanceFormValues,
type CancelRequestFormValues,
@@ -41,6 +43,7 @@ import {
canSupplementAcceptedEvidence,
getCopyOrderInfoItems,
getMaterialScreenshots,
getOrderShares,
getUniqueAcceptanceFiles,
getWorkerAcceptance,
getWorkerDraft,
@@ -165,6 +168,15 @@ export default function WorkerOrdersPage() {
const [pageSize, setPageSize] = useState(20)
const [detailOrder, setDetailOrder] = useState<WorkOrder | null>(null)
const [editingOrder, setEditingOrder] = useState<WorkOrder | null>(null)
/** 本次验收/补图针对的份额;null = 整单 */
const [editingShareId, setEditingShareId] = useState<number | null>(null)
/** 多份额时先让打手选择要操作哪一份 */
const [sharePicker, setSharePicker] = useState<{
order: WorkOrder
action: 'accept' | 'cancel'
} | null>(null)
const [pickedShareId, setPickedShareId] = useState<number>(0)
const [cancelShareId, setCancelShareId] = useState<number | null>(null)
const [noteOrder, setNoteOrder] = useState<WorkOrder | null>(null)
const [cancelRequestOrder, setCancelRequestOrder] = useState<WorkOrder | null>(null)
const [feedbackOrder, setFeedbackOrder] = useState<WorkOrder | null>(null)
@@ -292,10 +304,41 @@ export default function WorkerOrdersPage() {
}
}
/** 把订单对象替换成「指定份额」视角,让验收弹窗读写该份额的验收/草稿。 */
function buildShareEditingOrder(order: WorkOrder, shareId: number | null): WorkOrder {
if (!shareId) return order
const share = getOrderShares(order).find((item) => item.shareId === shareId)
if (!share) return order
return {
...order,
myShare: share,
// 已验收的份额走补图分支;其它份额沿用母单状态。
status: share.status === 'accepted' ? 'accepted' : order.status,
}
}
function openAcceptanceModal(order: WorkOrder) {
markOrderAsRead(order)
setEditingOrder(order)
if (canSupplementAcceptedEvidence(order)) {
const shares = getOrderShares(order)
if (shares.length > 1) {
setPickedShareId(shares[0]?.shareId || 0)
setSharePicker({ order, action: 'accept' })
return
}
openAcceptanceWithShare(order, shares[0]?.shareId ?? null)
}
/** 子表里直接对某一份份额发起验收/补图。 */
function openAcceptanceForShare(order: WorkOrder, shareId: number) {
markOrderAsRead(order)
openAcceptanceWithShare(order, shareId)
}
function openAcceptanceWithShare(order: WorkOrder, shareId: number | null) {
const editingTarget = buildShareEditingOrder(order, shareId)
setEditingShareId(shareId)
setEditingOrder(editingTarget)
if (canSupplementAcceptedEvidence(editingTarget)) {
// 已验收补图只上传新增文件,原有验收图片由服务端保留。
setAcceptanceFiles([])
form.resetFields()
@@ -303,9 +346,9 @@ export default function WorkerOrdersPage() {
return
}
// 已提交:以已提交的验收资料为初始内容;未提交:以暂存草稿为初始内容
const source = hasSubmittedAcceptance(order)
? getWorkerAcceptance(order)
: getWorkerDraft(order)
const source = hasSubmittedAcceptance(editingTarget)
? getWorkerAcceptance(editingTarget)
: getWorkerDraft(editingTarget)
setAcceptanceFiles(getUniqueAcceptanceFiles(source))
form.setFieldsValue({ note: String(source?.note || '') })
setDetailOrder(null)
@@ -356,6 +399,21 @@ export default function WorkerOrdersPage() {
function openCancelRequestModal(order: WorkOrder) {
markOrderAsRead(order)
const shares = getOrderShares(order)
if (shares.length > 1) {
setPickedShareId(shares[0]?.shareId || 0)
setSharePicker({ order, action: 'cancel' })
return
}
setCancelShareId(shares[0]?.shareId ?? null)
setCancelRequestOrder(order)
cancelRequestForm.resetFields()
}
/** 子表里直接对某一份份额申请取消。 */
function openCancelRequestForShare(order: WorkOrder, shareId: number) {
markOrderAsRead(order)
setCancelShareId(shareId)
setCancelRequestOrder(order)
cancelRequestForm.resetFields()
}
@@ -365,6 +423,7 @@ export default function WorkerOrdersPage() {
try {
await submitWorkerCancelRequest(cancelRequestOrder.workOrderId, {
reason: String(values.reason || '').trim(),
shareId: cancelShareId,
})
message.success('取消申请已提交,等待客服审核')
setCancelRequestOrder(null)
@@ -430,6 +489,7 @@ export default function WorkerOrdersPage() {
try {
await supplementWorkerAcceptedOrderEvidence(editingOrder.workOrderId, {
files: acceptanceFiles,
...(editingShareId ? { shareId: editingShareId } : {}),
})
message.success('验收图片已补充')
setEditingOrder(null)
@@ -456,6 +516,7 @@ export default function WorkerOrdersPage() {
await submitWorkerAcceptance(editingOrder.workOrderId, {
note: values.note,
files: acceptanceFiles,
...(editingShareId ? { shareId: editingShareId } : {}),
})
message.success(canAutoAccept ? '已自动验收,可在 24 小时内补充图片' : '已提交验收')
setEditingOrder(null)
@@ -477,6 +538,7 @@ export default function WorkerOrdersPage() {
await saveWorkerAcceptanceDraft(editingOrder.workOrderId, {
note: values.note,
files: acceptanceFiles,
...(editingShareId ? { shareId: editingShareId } : {}),
})
message.success(
hasSubmittedAcceptance(editingOrder) ? '验收图片已更新' : '验收图片已暂存,可继续补充',
@@ -501,6 +563,20 @@ export default function WorkerOrdersPage() {
setPage(1)
}
function renderShareTable(order: WorkOrder) {
return (
<WorkerOrderShareTable
order={order}
mobile={isMobile}
onOpenAcceptance={openAcceptanceForShare}
onOpenCancelRequest={openCancelRequestForShare}
onRemindAcceptance={(target) => {
void submitAcceptanceReminder(target)
}}
/>
)
}
const columns = buildWorkerOrderTableColumns({
onOpenDetail: openOrderDetail,
onOpenAcceptance: openAcceptanceModal,
@@ -562,6 +638,7 @@ export default function WorkerOrdersPage() {
pagination={pagination}
page={page}
pageSize={pageSize}
renderShareTable={renderShareTable}
loading={ordersQuery.isLoading}
searching={ordersQuery.isFetching}
refreshing={ordersQuery.isFetching || overviewQuery.isFetching}
@@ -611,6 +688,7 @@ export default function WorkerOrdersPage() {
page={page}
pageSize={pageSize}
columns={columns}
renderExpandedRow={renderShareTable}
loading={ordersQuery.isLoading}
searching={ordersQuery.isFetching}
refreshing={ordersQuery.isFetching || overviewQuery.isFetching}
@@ -695,6 +773,57 @@ export default function WorkerOrdersPage() {
onGiftMaterialFilesChange={setGiftMaterialFiles}
onSubmitGiftMaterial={submitGiftMaterial}
/>
<Modal
open={Boolean(sharePicker)}
title={sharePicker?.action === 'cancel' ? '选择要撤销的份额' : '选择要验收的份额'}
okText={sharePicker?.action === 'cancel' ? '提交退单申请' : '继续'}
cancelText="取消"
onCancel={() => setSharePicker(null)}
onOk={() => {
if (!sharePicker) return
if (sharePicker.action === 'accept') {
openAcceptanceWithShare(sharePicker.order, pickedShareId || null)
} else {
setCancelShareId(pickedShareId || null)
setCancelRequestOrder(sharePicker.order)
cancelRequestForm.resetFields()
}
setSharePicker(null)
}}
>
<Radio.Group
value={pickedShareId}
onChange={(event) => setPickedShareId(Number(event.target.value))}
style={{ display: 'flex', flexDirection: 'column', gap: 10 }}
>
{sharePicker && sharePicker.action === 'cancel' ? (
<Radio value={0}>全部有效份额(整单退单)</Radio>
) : null}
{sharePicker
? getOrderShares(sharePicker.order).map((share) => (
<Radio key={share.shareId} value={share.shareId}>
<Space size={6} wrap>
<span>#{share.shareId}</span>
<Tag color="purple">{share.quantity} 份</Tag>
<span>
{share.status === 'joined'
? '进行中'
: share.status === 'submitted'
? '待验收'
: share.status === 'accepted'
? '已验收'
: share.status}
</span>
<span style={{ color: '#8c8c8c' }}>
{share.deadlineAt ? `${formatDateTime(share.deadlineAt)} 截止` : '不限时'}
</span>
</Space>
</Radio>
))
: null}
</Radio.Group>
</Modal>
</>
)
}
@@ -6,6 +6,7 @@ import type {
UploadedFile,
WorkerAcceptance,
WorkOrder,
WorkOrderShare,
} from '@/types/worker-platform'
import { getImageIdentity } from '@/utils/image-identity'
@@ -33,6 +34,13 @@ export function patchWorkOrderReadState(current: unknown, workOrderId: number) {
}
}
/** 同一打手在该工单的全部有效份额;单份额订单回退为 myShare 一条。 */
export function getOrderShares(order: WorkOrder | null | undefined): WorkOrderShare[] {
if (!order) return []
if (order.myShares?.length) return order.myShares
return order.myShare ? [order.myShare] : []
}
/** 好友赠送验收模式(整单,不参与拼单)。 */
export function isFriendGiftOrder(order: WorkOrder) {
return order.acceptanceMode === 'friend_gift' && !order.myShare
+20 -11
View File
@@ -220,12 +220,18 @@ export function fetchWorkerHallLeaderboard(period: WorkerLeaderboardPeriod = 'al
return apiGet<WorkerLeaderboardResponse>('/api/v1/worker/hall/leaderboard', { period })
}
export function grabWorkerOrder(workOrderId: number) {
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/hall/orders/${workOrderId}/grab`)
export function grabWorkerOrder(workOrderId: number, payload: { requestId?: string } = {}) {
return apiPost<{ order: WorkOrder; duplicated?: boolean }>(
`/api/v1/worker/hall/orders/${workOrderId}/grab`,
payload,
)
}
export function joinWorkerSharingOrder(workOrderId: number, payload: { quantity: number }) {
return apiPost<{ share: WorkOrderShare }>(
export function joinWorkerSharingOrder(
workOrderId: number,
payload: { quantity: number; requestId?: string },
) {
return apiPost<{ share: WorkOrderShare; duplicated?: boolean }>(
`/api/v1/worker/hall/orders/${workOrderId}/join-sharing`,
payload,
)
@@ -249,7 +255,10 @@ export function fetchWorkerCancelRequests() {
)
}
export function submitWorkerCancelRequest(workOrderId: number, payload: { reason: string }) {
export function submitWorkerCancelRequest(
workOrderId: number,
payload: { reason: string; shareId?: number | null },
) {
return apiPost<{ request: WorkerCancelRequest; risk: WorkerCancelRequestRisk }>(
`/api/v1/worker/orders/${workOrderId}/cancel-requests`,
payload,
@@ -294,9 +303,9 @@ export function saveWorkerOrderNote(workOrderId: number, note: string) {
export function submitWorkerAcceptance(
workOrderId: number,
payload: { note?: string; imageUrls?: string[]; files?: UploadedFile[] },
payload: { note?: string; imageUrls?: string[]; files?: UploadedFile[]; shareId?: number },
) {
return apiPost<{ order: WorkOrder }>(
return apiPost<{ order: WorkOrder; share?: WorkOrderShare }>(
`/api/v1/worker/orders/${workOrderId}/submit-acceptance`,
payload,
)
@@ -321,9 +330,9 @@ export function remindWorkerAcceptance(workOrderId: number) {
export function saveWorkerAcceptanceDraft(
workOrderId: number,
payload: { note?: string; imageUrls?: string[]; files?: UploadedFile[] },
payload: { note?: string; imageUrls?: string[]; files?: UploadedFile[]; shareId?: number },
) {
return apiPost<{ order: WorkOrder }>(
return apiPost<{ order: WorkOrder; share?: WorkOrderShare }>(
`/api/v1/worker/orders/${workOrderId}/acceptance-draft`,
payload,
)
@@ -331,9 +340,9 @@ export function saveWorkerAcceptanceDraft(
export function supplementWorkerAcceptedOrderEvidence(
workOrderId: number,
payload: { files: UploadedFile[] },
payload: { files: UploadedFile[]; shareId?: number },
) {
return apiPost<{ order: WorkOrder }>(
return apiPost<{ order: WorkOrder; share?: WorkOrderShare }>(
`/api/v1/worker/orders/${workOrderId}/acceptance-evidence`,
payload,
)
+23
View File
@@ -4853,3 +4853,26 @@ body {
padding-top: 6px;
border-top: 1px dashed #bfdbfe;
}
/* 我的订单:一行一工单,展开后的份额子表 */
.worker-orders-table .ant-table-expanded-row > td {
background: #fafafa;
}
.worker-order-share-table .ant-table {
font-size: 12px;
}
.worker-order-share-table .ant-table-thead > tr > th {
background: #f5f5f5;
font-size: 12px;
}
.worker-order-mobile-shares {
display: flex;
flex-direction: column;
gap: 6px;
margin: 8px 12px 4px;
padding-top: 8px;
border-top: 1px dashed #f0f0f0;
}
@@ -297,6 +297,7 @@ export type WorkerPlatformNotificationEventKey =
| 'recharge_requested'
| 'acceptance_submitted'
| 'acceptance_reminded'
| 'order_accepted'
| 'material_required'
| 'cancel_requested'
| 'feedback_submitted'
@@ -539,6 +540,11 @@ export type WorkOrderShare = {
acceptedAt: string | null
createdAt: string
updatedAt: string
/** 本份额独立截止时间(参与时固化);null = 不限时 */
deadlineAt?: string | null
timeoutMinutes?: number
timeoutPolicy?: string
cancelledReason?: string
worker: null | {
workerId: number
username: string
@@ -620,6 +626,8 @@ export type WorkOrder = {
freezeDepositAmount?: number
sharing?: WorkOrderSharingConfig
myShare?: WorkOrderShare | null
/** 同一打手在该工单的全部有效份额(一行一工单 + 展开份额) */
myShares?: WorkOrderShare[]
sharingProgress?: {
joinedQuantity: number
pendingSubmissionCount: number