完善拼单状态与份额验收流程
This commit is contained in:
@@ -45,6 +45,7 @@ import ImageUpload from '@/components/files/ImageUpload'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import {
|
||||
acceptAdminWorkOrder,
|
||||
acceptAdminWorkOrderShare,
|
||||
acceptAdminWorkOrders,
|
||||
assignAdminWorkOrderToWorker,
|
||||
cancelAdminWorkOrder,
|
||||
@@ -741,16 +742,18 @@ export default function WorkOrdersPanel() {
|
||||
{
|
||||
title: '状态',
|
||||
width: 150,
|
||||
render: (_, row) => (
|
||||
<Space wrap size={4}>
|
||||
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
|
||||
{row.status === 'open' && row.pinnedAt ? (
|
||||
<Tag color="gold" icon={<PushpinOutlined />}>
|
||||
置顶
|
||||
</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
render: (_, row) => {
|
||||
return (
|
||||
<Space wrap size={4}>
|
||||
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
|
||||
{row.rawStatus === 'open' && row.pinnedAt ? (
|
||||
<Tag color="gold" icon={<PushpinOutlined />}>
|
||||
置顶
|
||||
</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '打手',
|
||||
@@ -768,6 +771,16 @@ export default function WorkOrdersPanel() {
|
||||
render: (_, row) => {
|
||||
const isVipAutoAcceptance = row.acceptance?.reviewMode === 'vip_auto'
|
||||
const evidencePending = row.acceptance?.evidenceStatus === 'pending'
|
||||
if (row.sharing?.enabled) {
|
||||
return (
|
||||
<Button type="link" size="small" onClick={() => openSharingConfigModal(row)}>
|
||||
查看拼单验收图
|
||||
{row.sharingProgress?.pendingSubmissionCount
|
||||
? `(${row.sharingProgress.pendingSubmissionCount}人待提交)`
|
||||
: ''}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Space direction="vertical" size={4}>
|
||||
<ImagePreviewList
|
||||
@@ -791,37 +804,37 @@ export default function WorkOrdersPanel() {
|
||||
render: (_, row) => (
|
||||
<Space wrap>
|
||||
<Button onClick={() => openDetailDrawer(row)}>详情</Button>
|
||||
{['pending_material', 'unassigned', 'open'].includes(row.status) ? (
|
||||
{row.capabilities?.canEdit ? (
|
||||
<Button icon={<EditOutlined />} onClick={() => openEditModal(row)}>
|
||||
编辑
|
||||
</Button>
|
||||
) : null}
|
||||
{['pending_material', 'unassigned', 'cancelled'].includes(row.status) ? (
|
||||
{row.capabilities?.canDelete ? (
|
||||
<Button danger icon={<DeleteOutlined />} onClick={() => requestDeleteWorkOrder(row)}>
|
||||
删除
|
||||
</Button>
|
||||
) : null}
|
||||
{['pending_material', 'unassigned', 'open'].includes(row.status) ? (
|
||||
{row.sharing?.enabled || row.capabilities?.canConfigureSharing ? (
|
||||
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
||||
拼单配置
|
||||
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing ? '拼单明细' : '拼单配置'}
|
||||
</Button>
|
||||
) : null}
|
||||
{['unassigned', 'open'].includes(row.status) ? (
|
||||
{row.capabilities?.canAssign ? (
|
||||
<Button icon={<UserAddOutlined />} onClick={() => openAssignModal(row)}>
|
||||
指派并核销
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'in_progress' && row.worker ? (
|
||||
{row.capabilities?.canCancel ? (
|
||||
<Button danger icon={<UndoOutlined />} onClick={() => openCancelModal(row)}>
|
||||
撤单
|
||||
</Button>
|
||||
) : null}
|
||||
{['pending_material', 'in_progress'].includes(row.status) ? (
|
||||
{row.capabilities?.canUpdateMaterial ? (
|
||||
<Button icon={<FormOutlined />} onClick={() => openMaterialModal(row)}>
|
||||
{row.status === 'in_progress' ? '更新资料' : '补资料'}
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'unassigned' ? (
|
||||
{row.capabilities?.canPublish ? (
|
||||
<Button
|
||||
icon={<SendOutlined />}
|
||||
disabled={Number(row.rewardAmount || 0) <= 0}
|
||||
@@ -833,7 +846,7 @@ export default function WorkOrdersPanel() {
|
||||
发布并核销
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'open' ? (
|
||||
{row.capabilities?.canPin ? (
|
||||
<Button
|
||||
icon={<PushpinOutlined />}
|
||||
title={row.pinnedAt ? '取消置顶' : '置顶后抢单大厅优先展示'}
|
||||
@@ -847,7 +860,7 @@ export default function WorkOrdersPanel() {
|
||||
{row.pinnedAt ? '取消置顶' : '置顶'}
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'open' ? (
|
||||
{row.capabilities?.canUnpublish ? (
|
||||
<Button
|
||||
danger
|
||||
onClick={() => {
|
||||
@@ -864,7 +877,7 @@ export default function WorkOrdersPanel() {
|
||||
下架
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'pending_acceptance' ? (
|
||||
{row.capabilities?.canAccept ? (
|
||||
<Button
|
||||
icon={<CheckOutlined />}
|
||||
type="primary"
|
||||
@@ -873,17 +886,17 @@ export default function WorkOrdersPanel() {
|
||||
验收
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'accepted' && Number(row.pendingUnfreezeAmount || 0) > 0 ? (
|
||||
{row.capabilities?.canDeduct && Number(row.pendingUnfreezeAmount || 0) > 0 ? (
|
||||
<Button icon={<WarningOutlined />} danger onClick={() => openDeductModal(row)}>
|
||||
扣押金
|
||||
</Button>
|
||||
) : null}
|
||||
{['in_progress', 'pending_acceptance'].includes(row.status) ? (
|
||||
{row.capabilities?.canMarkProblem ? (
|
||||
<Button icon={<WarningOutlined />} danger onClick={() => setProblemOrder(row)}>
|
||||
问题单
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'problem' ? (
|
||||
{row.capabilities?.canResolveProblem ? (
|
||||
<Button icon={<FormOutlined />} onClick={() => setResolutionOrder(row)}>
|
||||
处置
|
||||
</Button>
|
||||
@@ -1572,13 +1585,14 @@ export default function WorkOrdersPanel() {
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`拼单配置 · ${sharingOrder?.productName || ''}`}
|
||||
title={`${sharingOrder?.capabilities?.canConfigureSharing ? '拼单配置' : '拼单明细'} · ${sharingOrder?.productName || ''}`}
|
||||
open={Boolean(sharingOrder)}
|
||||
onCancel={() => {
|
||||
setSharingOrder(null)
|
||||
sharingForm.resetFields()
|
||||
}}
|
||||
onOk={() => sharingForm.submit()}
|
||||
footer={sharingOrder?.capabilities?.canConfigureSharing ? undefined : null}
|
||||
destroyOnHidden
|
||||
width={720}
|
||||
>
|
||||
@@ -1593,6 +1607,7 @@ export default function WorkOrdersPanel() {
|
||||
unitReward: 0,
|
||||
}}
|
||||
onFinish={submitSharingConfig}
|
||||
disabled={!sharingOrder.capabilities?.canConfigureSharing}
|
||||
>
|
||||
<Form.Item
|
||||
label="启用拼单"
|
||||
@@ -1689,6 +1704,54 @@ export default function WorkOrdersPanel() {
|
||||
render: (_, share) =>
|
||||
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
||||
},
|
||||
{
|
||||
title: '验收图',
|
||||
width: 150,
|
||||
render: (_, share) => (
|
||||
<ImagePreviewList
|
||||
files={Array.isArray(share.acceptance?.files) ? share.acceptance.files : []}
|
||||
imageUrls={Array.isArray(share.acceptance?.imageUrls) ? share.acceptance.imageUrls : []}
|
||||
size={44}
|
||||
maxVisible={2}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
render: (_, share) => String(share.acceptance?.note || '').trim() || '-',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 110,
|
||||
render: (_, share) =>
|
||||
share.status === 'submitted' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
void runAction(
|
||||
() => acceptAdminWorkOrderShare(sharingOrder.workOrderId, share.shareId),
|
||||
'该拼单参与者已验收',
|
||||
).then((succeeded) => {
|
||||
if (succeeded) {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
'admin-worker-platform-order-sharing',
|
||||
sharingOrder.workOrderId,
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
单独验收
|
||||
</Button>
|
||||
) : share.status === 'accepted' ? (
|
||||
<Tag color="green">已验收</Tag>
|
||||
) : (
|
||||
<Typography.Text type="secondary">待提交</Typography.Text>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
@@ -1796,8 +1859,7 @@ function ProductNameEditableCell({
|
||||
const savingRef = useRef(false)
|
||||
const clickTimerRef = useRef<number | null>(null)
|
||||
|
||||
const editable =
|
||||
['pending_material', 'unassigned', 'open'].includes(order.status) && !order.worker
|
||||
const editable = order.capabilities?.canEdit === true
|
||||
|
||||
function handleClick() {
|
||||
if (clickTimerRef.current) window.clearTimeout(clickTimerRef.current)
|
||||
@@ -1880,8 +1942,7 @@ function AmountEditableCell({
|
||||
const [draft, setDraft] = useState<number | null>(null)
|
||||
const savingRef = useRef(false)
|
||||
|
||||
const editable =
|
||||
['pending_material', 'unassigned', 'open'].includes(order.status) && !order.worker
|
||||
const editable = order.capabilities?.canEdit === true
|
||||
const currentFen = Number(order[field] || 0)
|
||||
const currentYuan = currentFen / 100
|
||||
const paymentFen = field === 'rewardAmount' ? getUserPaymentFen(order) : null
|
||||
|
||||
@@ -36,6 +36,12 @@ import {
|
||||
} from '@/services/worker'
|
||||
import type { WorkOrder, WorkerUser } from '@/types/worker-platform'
|
||||
import { useIsMobile } from '@/utils/use-is-mobile'
|
||||
import {
|
||||
resolveHallDepositAmount,
|
||||
resolveHallProductName,
|
||||
resolveHallRemainingQuantity,
|
||||
resolveHallRewardAmount,
|
||||
} from '@/utils/worker-product-name'
|
||||
|
||||
type HallSummary = {
|
||||
pendingAcceptanceCount: number
|
||||
@@ -203,10 +209,7 @@ export default function WorkerHallPage() {
|
||||
}
|
||||
|
||||
function resolveSharingRemaining(order: WorkOrder): number {
|
||||
const sharing = order.sharing
|
||||
if (!sharing?.enabled) return 0
|
||||
const progress = order.sharingProgress
|
||||
return Math.max(0, sharing.totalQuantity - (progress?.joinedQuantity || 0))
|
||||
return resolveHallRemainingQuantity(order)
|
||||
}
|
||||
|
||||
function resolveSharingDeposit(order: WorkOrder, quantity: number): number {
|
||||
@@ -387,6 +390,8 @@ export default function WorkerHallPage() {
|
||||
) : (
|
||||
orders.map((order) => {
|
||||
const disabledReason = resolveGrabDisabledReason(order, worker, remainingSlots)
|
||||
const productName = resolveHallProductName(order) || '未命名商品'
|
||||
const sharingRemaining = resolveSharingRemaining(order)
|
||||
|
||||
return (
|
||||
<article key={order.workOrderId} className="worker-hall-mobile-card">
|
||||
@@ -409,7 +414,8 @@ export default function WorkerHallPage() {
|
||||
) : null}
|
||||
{order.sharing?.enabled ? (
|
||||
<Tag color="purple" className="worker-hall-mobile-tag">
|
||||
拼单 剩{resolveSharingRemaining(order)}份
|
||||
拼单 {order.sharingProgress?.joinedQuantity || 0}/
|
||||
{order.sharing.totalQuantity} · 剩{resolveSharingRemaining(order)}份
|
||||
</Tag>
|
||||
) : null}
|
||||
{Number(order.timeoutMinutes || 0) > 0 ? (
|
||||
@@ -429,14 +435,11 @@ export default function WorkerHallPage() {
|
||||
|
||||
{/* 商品标题与保证金说明 */}
|
||||
<div className="worker-hall-mobile-card-main">
|
||||
<strong
|
||||
className="worker-hall-mobile-card-title"
|
||||
title={order.productName || '未命名商品'}
|
||||
>
|
||||
{order.productName || '未命名商品'}
|
||||
<strong className="worker-hall-mobile-card-title" title={productName}>
|
||||
{productName}
|
||||
</strong>
|
||||
<div className="worker-hall-mobile-card-meta">
|
||||
<span>所需保证金: {formatMoney(order.freezeDepositAmount)}</span>
|
||||
<span>所需保证金: {formatMoney(resolveHallDepositAmount(order))}</span>
|
||||
{order.sharing?.enabled ? (
|
||||
<span> · 拼单单价 {formatMoney(order.sharing.unitReward)}/份</span>
|
||||
) : null}
|
||||
@@ -448,12 +451,14 @@ export default function WorkerHallPage() {
|
||||
<div className="worker-hall-mobile-card-price-group">
|
||||
<span className="worker-hall-mobile-card-price-label">接单报酬</span>
|
||||
<strong className="worker-hall-mobile-card-price-val">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
{formatMoney(resolveHallRewardAmount(order))}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div className="worker-hall-mobile-card-btn-group">
|
||||
{order.sharing?.enabled && !disabledReason ? (
|
||||
{order.sharing?.enabled &&
|
||||
(sharingRemaining > 0 || order.myShare) &&
|
||||
!disabledReason ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
@@ -466,16 +471,18 @@ export default function WorkerHallPage() {
|
||||
: `拼单 ${formatMoney(order.sharing.unitReward)}/份`}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
className="worker-hall-mobile-grab-btn"
|
||||
disabled={Boolean(disabledReason)}
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => submitGrab(order)}
|
||||
>
|
||||
{disabledReason || '立即抢单'}
|
||||
</Button>
|
||||
{sharingRemaining > 0 || !order.sharing?.enabled ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
className="worker-hall-mobile-grab-btn"
|
||||
disabled={Boolean(disabledReason)}
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => submitGrab(order)}
|
||||
>
|
||||
{disabledReason || '立即抢单'}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -613,6 +620,8 @@ export default function WorkerHallPage() {
|
||||
<div className="worker-hall-card-grid">
|
||||
{orders.map((order) => {
|
||||
const disabledReason = resolveGrabDisabledReason(order, worker, remainingSlots)
|
||||
const productName = resolveHallProductName(order) || '未命名商品'
|
||||
const sharingRemaining = resolveSharingRemaining(order)
|
||||
|
||||
return (
|
||||
<article key={order.workOrderId} className="worker-hall-card">
|
||||
@@ -626,11 +635,8 @@ export default function WorkerHallPage() {
|
||||
置顶
|
||||
</Tag>
|
||||
) : null}
|
||||
<strong
|
||||
className="worker-hall-card-title-text"
|
||||
title={order.productName || '未命名商品'}
|
||||
>
|
||||
{order.productName || '未命名商品'}
|
||||
<strong className="worker-hall-card-title-text" title={productName}>
|
||||
{productName}
|
||||
</strong>
|
||||
<Typography.Text type="secondary">
|
||||
{order.categoryName || '默认分类'}
|
||||
@@ -661,10 +667,10 @@ export default function WorkerHallPage() {
|
||||
|
||||
<div className="worker-hall-card-amount-row">
|
||||
<div className="worker-hall-card-amount">
|
||||
{formatMoney(order.rewardAmount)}
|
||||
{formatMoney(resolveHallRewardAmount(order))}
|
||||
</div>
|
||||
<Typography.Text type="secondary" className="worker-hall-card-deposit">
|
||||
所需保证金:{formatMoney(order.freezeDepositAmount)}
|
||||
所需保证金:{formatMoney(resolveHallDepositAmount(order))}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
@@ -676,7 +682,9 @@ export default function WorkerHallPage() {
|
||||
|
||||
<div className="worker-hall-card-actions">
|
||||
<div className="worker-hall-card-grab-row">
|
||||
{order.sharing?.enabled && !disabledReason ? (
|
||||
{order.sharing?.enabled &&
|
||||
(sharingRemaining > 0 || order.myShare) &&
|
||||
!disabledReason ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
@@ -689,15 +697,17 @@ export default function WorkerHallPage() {
|
||||
: `拼单 ${formatMoney(order.sharing.unitReward)}/份`}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
disabled={Boolean(disabledReason)}
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => submitGrab(order)}
|
||||
>
|
||||
{disabledReason || '立即抢单'}
|
||||
</Button>
|
||||
{sharingRemaining > 0 || !order.sharing?.enabled ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
disabled={Boolean(disabledReason)}
|
||||
loading={grabbingId === order.workOrderId}
|
||||
onClick={() => submitGrab(order)}
|
||||
>
|
||||
{disabledReason || '立即抢单'}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -713,7 +723,7 @@ export default function WorkerHallPage() {
|
||||
)}
|
||||
|
||||
<Modal
|
||||
title={`拼单参与 · ${sharingOrder?.productName || ''}`}
|
||||
title={`拼单参与 · ${sharingOrder ? resolveHallProductName(sharingOrder) : ''}`}
|
||||
width={isMobile ? '92%' : 520}
|
||||
open={Boolean(sharingOrder)}
|
||||
confirmLoading={submittingSharing}
|
||||
@@ -831,7 +841,7 @@ function resolveGrabDisabledReason(
|
||||
worker: WorkerUser | undefined,
|
||||
remainingSlots: number | null,
|
||||
) {
|
||||
if (remainingSlots !== null && remainingSlots <= 0) {
|
||||
if (remainingSlots !== null && remainingSlots <= 0 && !order.myShare) {
|
||||
return '已达到当前等级最大同时接单量'
|
||||
}
|
||||
|
||||
@@ -840,7 +850,7 @@ function resolveGrabDisabledReason(
|
||||
}
|
||||
|
||||
const availableAmount = Number(worker.wallet.availableAmount || 0)
|
||||
const freezeAmount = Number(order.freezeDepositAmount || 0)
|
||||
const freezeAmount = resolveHallDepositAmount(order)
|
||||
if (availableAmount < freezeAmount) {
|
||||
return `余额不足,还差 ${formatMoney(freezeAmount - availableAmount)}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user