支持VIP5免审核后补图

This commit is contained in:
yml2213
2026-08-17 12:29:34 +08:00
parent 2881a99040
commit baf09dc533
13 changed files with 348 additions and 28 deletions
@@ -129,6 +129,19 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
)}`,
color: 'purple',
}
case 'accepted_by_vip':
return {
title: 'VIP 免审核验收通过,订单完成',
description: readPayload(event, 'evidenceDueAt') ? '可在 24 小时内补充验收图片' : undefined,
color: 'purple',
}
case 'acceptance_evidence_supplemented': {
const addedImageCount = readPayload(event, 'addedImageCount')
return {
title: `已验收后补充验收图片${addedImageCount ? `(新增 ${addedImageCount} 张)` : ''}`,
color: 'purple',
}
}
case 'accepted':
case 'accepted_by_admin':
return {
@@ -8,6 +8,7 @@ import {
InputNumber,
Modal,
Space,
Switch,
Table,
Tag,
Typography,
@@ -46,6 +47,7 @@ export default function LevelsPanel() {
depositFreeAmount: level.depositFreeAmount,
maxActiveOrders: level.maxActiveOrders,
upgradeThreshold: level.upgradeThreshold,
autoAcceptWithoutEvidence: level.autoAcceptWithoutEvidence,
visibleDelaySeconds: level.visibleDelaySeconds,
})
}
@@ -61,6 +63,7 @@ export default function LevelsPanel() {
depositFreeAmount?: number
maxActiveOrders?: number
upgradeThreshold?: number
autoAcceptWithoutEvidence?: boolean
visibleDelaySeconds?: number
}) {
try {
@@ -81,6 +84,7 @@ export default function LevelsPanel() {
depositFreeAmount: level.depositFreeAmount,
maxActiveOrders: level.maxActiveOrders,
upgradeThreshold: level.upgradeThreshold,
autoAcceptWithoutEvidence: level.autoAcceptWithoutEvidence,
visibleDelaySeconds: level.visibleDelaySeconds,
status: level.status === 'active' ? 'disabled' : 'active',
})
@@ -116,6 +120,12 @@ export default function LevelsPanel() {
},
{ title: '最大同时接单', dataIndex: 'maxActiveOrders' },
{ title: '升级所需累计完成', dataIndex: 'upgradeThreshold' },
{
title: '免审核后补图',
width: 130,
render: (_, row) =>
row.autoAcceptWithoutEvidence ? <Tag color="purple"></Tag> : <Tag></Tag>,
},
{
title: '可见延迟',
width: 110,
@@ -161,6 +171,7 @@ export default function LevelsPanel() {
depositFreeAmount: 0,
maxActiveOrders: 1,
upgradeThreshold: 0,
autoAcceptWithoutEvidence: false,
visibleDelaySeconds: 20,
}}
onFinish={saveLevel}
@@ -188,6 +199,14 @@ export default function LevelsPanel() {
>
<InputNumber min={0} max={100000} />
</Form.Item>
<Form.Item
label="免审核后补图"
name="autoAcceptWithoutEvidence"
valuePropName="checked"
tooltip="开启后,打手可无验收图提交并自动验收;验收后仅可在 24 小时内补图。"
>
<Switch />
</Form.Item>
<Form.Item
label="大厅可见延迟(秒)"
name="visibleDelaySeconds"
@@ -727,14 +727,25 @@ export default function WorkOrdersPanel() {
{
title: '验收图',
width: 170,
render: (_, row) => (
<ImagePreviewList
files={getAcceptanceFiles(row)}
imageUrls={getAcceptanceImageUrls(row)}
size={44}
maxVisible={2}
/>
),
render: (_, row) => {
const isVipAutoAcceptance = row.acceptance?.reviewMode === 'vip_auto'
const evidencePending = row.acceptance?.evidenceStatus === 'pending'
return (
<Space direction="vertical" size={4}>
<ImagePreviewList
files={getAcceptanceFiles(row)}
imageUrls={getAcceptanceImageUrls(row)}
size={44}
maxVisible={2}
/>
{isVipAutoAcceptance ? (
<Tag color="purple">
{evidencePending ? 'VIP 免审核,待补图' : 'VIP 免审核,已补图'}
</Tag>
) : null}
</Space>
)
},
},
{
title: '操作',
@@ -1083,6 +1094,13 @@ export default function WorkOrdersPanel() {
{getAcceptanceFiles(detailOrder).length +
getAcceptanceImageUrls(detailOrder).length || '-'}
</Descriptions.Item>
{detailOrder.acceptance?.reviewMode === 'vip_auto' ? (
<Descriptions.Item label="VIP 补图">
{detailOrder.acceptance.evidenceStatus === 'pending'
? `待补图,截止 ${formatAdminDateTime(detailOrder.acceptance.evidenceDueAt)}`
: `已补图 ${formatAdminDateTime(detailOrder.acceptance.evidenceSupplementedAt)}`}
</Descriptions.Item>
) : null}
<Descriptions.Item label="备注" span={2}>
<span className="worker-order-detail-value">
{String(detailOrder.acceptance?.note || '').trim() || '-'}
@@ -38,6 +38,7 @@ import {
fetchWorkerProfile,
saveWorkerAcceptanceDraft,
saveWorkerOrderNote,
supplementWorkerAcceptedOrderEvidence,
submitWorkerAcceptance,
} from '@/services/worker'
import type {
@@ -199,6 +200,8 @@ export default function WorkerOrdersPage() {
retry: false,
})
const orderTimelineVisible = profileQuery.data?.data.orderTimelineVisible === true
const workerCanAutoAcceptWithoutEvidence =
profileQuery.data?.data.worker.level?.permissions.autoAcceptWithoutEvidence === true
const overviewQuery = useQuery({
queryKey: ['worker-my-orders-overview'],
@@ -297,7 +300,29 @@ export default function WorkerOrdersPage() {
async function submitAcceptance(values: AcceptanceFormValues) {
if (!editingOrder) return
if (acceptanceFiles.length === 0) {
const isAcceptedEvidenceSupplement = canSupplementAcceptedEvidence(editingOrder)
if (isAcceptedEvidenceSupplement) {
if (acceptanceFiles.length === 0) {
message.error('请上传新的验收图片')
return
}
try {
await supplementWorkerAcceptedOrderEvidence(editingOrder.workOrderId, {
files: acceptanceFiles,
})
message.success('验收图片已补充')
setEditingOrder(null)
setAcceptanceFiles([])
form.resetFields()
await refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '补充验收图片失败')
}
return
}
const canAutoAccept = canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence)
if (acceptanceFiles.length === 0 && !canAutoAccept) {
message.error('请上传验收图片')
return
}
@@ -306,7 +331,7 @@ export default function WorkerOrdersPage() {
note: values.note,
files: acceptanceFiles,
})
message.success('已提交验收')
message.success(canAutoAccept ? '已自动验收,可在 24 小时内补充图片' : '已提交验收')
setEditingOrder(null)
setAcceptanceFiles([])
form.resetFields()
@@ -426,6 +451,7 @@ export default function WorkerOrdersPage() {
const hasAcceptance = acceptanceFiles.length > 0
const draftFiles = getUniqueAcceptanceFiles(getWorkerDraft(row))
const draftCount = draftFiles.length
const vipEvidencePending = isVipEvidencePending(row)
if (!hasAcceptance && !submittedAt && draftCount === 0) {
return (
@@ -444,6 +470,7 @@ export default function WorkerOrdersPage() {
{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 />}>
@@ -794,6 +821,9 @@ export default function WorkerOrdersPage() {
<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 />}>
@@ -1160,7 +1190,7 @@ export default function WorkerOrdersPage() {
<Modal
title={
editingOrder
? hasSubmittedAcceptance(editingOrder)
? canSupplementAcceptedEvidence(editingOrder) || hasSubmittedAcceptance(editingOrder)
? '补充验收图片'
: '验收图片'
: '验收图片'
@@ -1185,14 +1215,22 @@ export default function WorkerOrdersPage() {
>
</Button>
<Button onClick={() => void saveAcceptanceImages(form.getFieldsValue())}>
{hasSubmittedAcceptance(editingOrder) ? '保存修改' : '保存暂存'}
</Button>
{canSubmitAcceptance(editingOrder) ? (
{canSupplementAcceptedEvidence(editingOrder) ? (
<Button type="primary" onClick={() => form.submit()}>
</Button>
) : null}
) : (
<>
<Button onClick={() => void saveAcceptanceImages(form.getFieldsValue())}>
{hasSubmittedAcceptance(editingOrder) ? '保存修改' : '保存暂存'}
</Button>
{canSubmitAcceptance(editingOrder) ? (
<Button type="primary" onClick={() => form.submit()}>
</Button>
) : null}
</>
)}
</Space>
) : null
}
@@ -1201,7 +1239,17 @@ export default function WorkerOrdersPage() {
<Form.Item label="完成说明" name="note">
<Input.TextArea rows={3} placeholder="可补充代练结果、注意事项或截图说明。" />
</Form.Item>
<Form.Item label="验收图片" required>
<Form.Item
label="验收图片"
required={
!editingOrder || !canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence)
}
extra={
editingOrder && canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence)
? 'VIP 免审核可先提交,验收后需在 24 小时内补充图片。'
: undefined
}
>
<ImageUpload
scene="worker-acceptance"
scope="worker"
@@ -1224,12 +1272,38 @@ function canSubmitAcceptance(order: WorkOrder) {
return ['in_progress', 'problem'].includes(order.status)
}
function canAutoAcceptOrder(order: WorkOrder, workerCanAutoAcceptWithoutEvidence: boolean) {
return (
workerCanAutoAcceptWithoutEvidence &&
!order.myShare &&
order.sharing?.enabled !== true &&
['in_progress', 'problem'].includes(order.status)
)
}
function canSupplementAcceptedEvidence(order: WorkOrder) {
if (order.myShare || order.status !== 'accepted' || order.acceptance?.reviewMode !== 'vip_auto') {
return false
}
const dueAt = Date.parse(String(order.acceptance.evidenceDueAt || ''))
return Number.isFinite(dueAt) && dueAt > Date.now()
}
function isVipEvidencePending(order: WorkOrder) {
return (
order.acceptance?.reviewMode === 'vip_auto' && order.acceptance?.evidenceStatus === 'pending'
)
}
/** 是否可编辑验收图片:未提交时可暂存,已提交(待验收)时可补充/修改 */
function canEditAcceptanceImages(order: WorkOrder) {
if (order.myShare) {
return ['joined', 'submitted'].includes(order.myShare.status) && order.status !== 'accepted'
}
return ['in_progress', 'problem', 'pending_acceptance'].includes(order.status)
return (
['in_progress', 'problem', 'pending_acceptance'].includes(order.status) ||
canSupplementAcceptedEvidence(order)
)
}
/** 是否已提交验收(整单 pending_acceptance / 拼单份额 submitted */
@@ -1237,7 +1311,7 @@ function hasSubmittedAcceptance(order: WorkOrder) {
if (order.myShare) {
return order.myShare.status === 'submitted'
}
return order.status === 'pending_acceptance'
return order.status === 'pending_acceptance' || order.acceptance?.reviewMode === 'vip_auto'
}
function getDisplayOrderNo(order: Pick<WorkOrder, 'platformOrderId'> | null | undefined): string {
@@ -64,6 +64,7 @@ export function saveAdminWorkerLevel(payload: {
depositFreeAmount?: number
maxActiveOrders?: number
upgradeThreshold?: number
autoAcceptWithoutEvidence?: boolean
visibleDelaySeconds?: number
status?: string
}) {
+10
View File
@@ -159,6 +159,16 @@ export function saveWorkerAcceptanceDraft(
)
}
export function supplementWorkerAcceptedOrderEvidence(
workOrderId: number,
payload: { files: UploadedFile[] },
) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/worker/orders/${workOrderId}/acceptance-evidence`,
payload,
)
}
export function lookupCollectOrder(orderNo: string) {
return apiPost<CollectLookupResponse>('/api/v1/collect/lookup', { orderNo })
}
@@ -20,6 +20,7 @@ export type WorkerUser = {
depositFreeAmount: number
maxActiveOrders: number
upgradeThreshold: number
autoAcceptWithoutEvidence: boolean
visibleDelaySeconds: number
}
}
@@ -141,6 +142,7 @@ export type WorkerLevel = {
depositFreeAmount: number
maxActiveOrders: number
upgradeThreshold: number
autoAcceptWithoutEvidence: boolean
visibleDelaySeconds: number
createdAt: string
updatedAt: string
@@ -217,6 +219,10 @@ export type WorkerAcceptance = {
imageUrls?: string[]
submittedAt?: string
updatedAt?: string
reviewMode?: 'vip_auto' | string
evidenceStatus?: 'pending' | 'complete' | string
evidenceDueAt?: string | null
evidenceSupplementedAt?: string | null
[key: string]: unknown
}