add:新增同一个工单同一人可以多次拼单:拼单明细ui优化
This commit is contained in:
@@ -57,6 +57,7 @@ import {
|
||||
confirmAdminWorkOrderFriend,
|
||||
deductAdminWorkOrderPendingDeposit,
|
||||
deleteAdminWorkOrder,
|
||||
disposeAdminWorkOrderShareProblem,
|
||||
fetchAdminWorkCategories,
|
||||
fetchAdminSession,
|
||||
fetchAdminWorkOrderSharing,
|
||||
@@ -65,6 +66,7 @@ import {
|
||||
fetchAdminWorkerUsers,
|
||||
fetchAdminWorkOrders,
|
||||
markAdminWorkOrderProblem,
|
||||
markAdminWorkOrderShareProblem,
|
||||
pinAdminWorkOrder,
|
||||
publishAdminWorkOrder,
|
||||
reopenAdminCancelledWorkOrder,
|
||||
@@ -241,6 +243,9 @@ export default function WorkOrdersPanel() {
|
||||
const canCancelShare = sessionPermissions
|
||||
? sessionPermissions.includes('worker_order.cancel_share')
|
||||
: hasAdminPermission('worker_order.cancel_share')
|
||||
const canManageShareProblem = sessionPermissions
|
||||
? sessionPermissions.includes('worker_order.manage_problem')
|
||||
: hasAdminPermission('worker_order.manage_problem')
|
||||
const canEditOrder = sessionPermissions
|
||||
? sessionPermissions.includes('worker_order.edit')
|
||||
: hasAdminPermission('worker_order.edit')
|
||||
@@ -298,6 +303,8 @@ export default function WorkOrdersPanel() {
|
||||
const [materialPasteText, setMaterialPasteText] = useState('')
|
||||
const [sharingOrder, setSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [cancelShare, setCancelShare] = useState<WorkOrderShare | null>(null)
|
||||
const [problemShare, setProblemShare] = useState<WorkOrderShare | null>(null)
|
||||
const [disposeShare, setDisposeShare] = useState<WorkOrderShare | null>(null)
|
||||
const [cancelSharingOrder, setCancelSharingOrder] = useState<WorkOrder | null>(null)
|
||||
const [assignOrder, setAssignOrder] = useState<WorkOrder | null>(null)
|
||||
const [assignWorkerId, setAssignWorkerId] = useState<number | undefined>(undefined)
|
||||
@@ -310,6 +317,11 @@ export default function WorkOrdersPanel() {
|
||||
const [materialForm] = Form.useForm<MaterialFormValues>()
|
||||
const [cancelForm] = Form.useForm<CancelOrderFormValues>()
|
||||
const [cancelShareForm] = Form.useForm<CancelShareFormValues>()
|
||||
const [problemShareForm] = Form.useForm<{ note?: string }>()
|
||||
const [disposeShareForm] = Form.useForm<{
|
||||
action?: 'return_to_worker' | 'reopen' | 'cancel_release' | 'cancel_deduct'
|
||||
note?: string
|
||||
}>()
|
||||
const [cancelSharingForm] = Form.useForm<CancelSharingFormValues>()
|
||||
const [deductForm] = Form.useForm<{ amount?: number; note?: string }>()
|
||||
const [editForm] = Form.useForm<{
|
||||
@@ -710,6 +722,51 @@ export default function WorkOrdersPanel() {
|
||||
cancelShareForm.resetFields()
|
||||
}
|
||||
|
||||
/** 按份额标记问题单:只影响该打手,母单状态不变。备注选填。 */
|
||||
async function submitProblemShare(values: { note?: string }) {
|
||||
if (!sharingOrder || !problemShare) return
|
||||
const workOrderId = sharingOrder.workOrderId
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
markAdminWorkOrderShareProblem(
|
||||
workOrderId,
|
||||
problemShare.shareId,
|
||||
String(values.note || '').trim(),
|
||||
),
|
||||
'该份额已标记为问题单,仅影响该打手',
|
||||
)
|
||||
if (!succeeded) return
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['admin-worker-platform-order-sharing', workOrderId],
|
||||
})
|
||||
setProblemShare(null)
|
||||
problemShareForm.resetFields()
|
||||
}
|
||||
|
||||
/** 处置份额问题单:方式与母单处置一致,但只作用于这一份,母单状态不变。 */
|
||||
async function submitShareDisposal(values: {
|
||||
action?: 'return_to_worker' | 'reopen' | 'cancel_release' | 'cancel_deduct'
|
||||
note?: string
|
||||
}) {
|
||||
if (!sharingOrder || !disposeShare || !values.action) return
|
||||
const workOrderId = sharingOrder.workOrderId
|
||||
const action = values.action
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
disposeAdminWorkOrderShareProblem(workOrderId, disposeShare.shareId, {
|
||||
action,
|
||||
note: String(values.note || '').trim(),
|
||||
}),
|
||||
'该份额问题单已处置',
|
||||
)
|
||||
if (!succeeded) return
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['admin-worker-platform-order-sharing', workOrderId],
|
||||
})
|
||||
setDisposeShare(null)
|
||||
disposeShareForm.resetFields()
|
||||
}
|
||||
|
||||
async function submitCancelSharing(values: CancelSharingFormValues) {
|
||||
if (!cancelSharingOrder || !values.action) return
|
||||
const action = values.action
|
||||
@@ -3023,13 +3080,14 @@ export default function WorkOrdersPanel() {
|
||||
sharingOrder?.capabilities?.canConfigureSharing && canConfigureSharing ? undefined : null
|
||||
}
|
||||
destroyOnHidden
|
||||
width={720}
|
||||
width={1200}
|
||||
>
|
||||
{sharingOrder ? (
|
||||
<Space direction="vertical" size={16} className="full-width">
|
||||
<Form
|
||||
form={sharingForm}
|
||||
layout="vertical"
|
||||
style={{ maxWidth: 760 }}
|
||||
initialValues={{
|
||||
enabled: false,
|
||||
totalQuantity: 1,
|
||||
@@ -3146,20 +3204,33 @@ export default function WorkOrdersPanel() {
|
||||
dataSource={sharingShares}
|
||||
loading={sharingQuery.isFetching}
|
||||
pagination={false}
|
||||
scroll={{ x: 1280 }}
|
||||
tableLayout="fixed"
|
||||
scroll={{ x: 1528 }}
|
||||
rowClassName={(share) =>
|
||||
share.problemMarked
|
||||
? 'admin-share-row-problem'
|
||||
: share.status === 'cancelled'
|
||||
? 'admin-share-row-cancelled'
|
||||
: ''
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
title: '打手',
|
||||
width: 132,
|
||||
fixed: 'left',
|
||||
render: (_, share) =>
|
||||
share.worker?.displayName || share.worker?.username || '-',
|
||||
},
|
||||
{ title: '数量', dataIndex: 'quantity' },
|
||||
{ title: '数量', dataIndex: 'quantity', width: 64, align: 'center' },
|
||||
{
|
||||
title: '份额报酬',
|
||||
width: 96,
|
||||
align: 'right',
|
||||
render: (_, share) => formatMoney(share.shareReward),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 104,
|
||||
render: (_, share) => (
|
||||
<Space size={4} wrap>
|
||||
<Tag color={resolveSharingShareStatusColor(share.status)}>
|
||||
@@ -3171,13 +3242,40 @@ export default function WorkOrdersPanel() {
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '问题',
|
||||
width: 120,
|
||||
render: (_, share) =>
|
||||
share.problemMarked ? (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Tag color="red">问题单</Tag>
|
||||
{String(share.problemNote || '').trim() ? (
|
||||
<Typography.Text
|
||||
type="danger"
|
||||
style={{ fontSize: 12 }}
|
||||
ellipsis={{ tooltip: share.problemNote }}
|
||||
>
|
||||
{share.problemNote}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
未填备注
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
) : (
|
||||
<Typography.Text type="secondary">-</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '参与时间',
|
||||
width: 128,
|
||||
render: (_, share) =>
|
||||
share.createdAt ? formatAdminDateTime(share.createdAt) : '-',
|
||||
},
|
||||
{
|
||||
title: '截止时间',
|
||||
width: 150,
|
||||
render: (_, share) =>
|
||||
share.deadlineAt ? (
|
||||
<span>
|
||||
@@ -3190,17 +3288,19 @@ export default function WorkOrdersPanel() {
|
||||
},
|
||||
{
|
||||
title: '超时策略',
|
||||
width: 90,
|
||||
render: (_, share) =>
|
||||
share.timeoutPolicy ? formatTimeoutPolicyLabel(share.timeoutPolicy) : '-',
|
||||
},
|
||||
{
|
||||
title: '提交时间',
|
||||
width: 128,
|
||||
render: (_, share) =>
|
||||
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
||||
},
|
||||
{
|
||||
title: '验收图',
|
||||
width: 150,
|
||||
width: 96,
|
||||
render: (_, share) => (
|
||||
<ImagePreviewList
|
||||
files={
|
||||
@@ -3211,23 +3311,70 @@ export default function WorkOrdersPanel() {
|
||||
? share.acceptance.imageUrls
|
||||
: []
|
||||
}
|
||||
size={44}
|
||||
size={36}
|
||||
maxVisible={2}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
render: (_, share) => String(share.acceptance?.note || '').trim() || '-',
|
||||
width: 120,
|
||||
render: (_, share) => {
|
||||
const note = String(share.acceptance?.note || '').trim()
|
||||
return note ? (
|
||||
<Typography.Text style={{ fontSize: 12 }} ellipsis={{ tooltip: note }}>
|
||||
{note}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
'-'
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 190,
|
||||
width: 300,
|
||||
fixed: 'right',
|
||||
render: (_, share) => {
|
||||
if (share.status === 'accepted') return <Tag color="green">已验收</Tag>
|
||||
if (share.status === 'cancelled') return <Tag>已撤单</Tag>
|
||||
if (share.status === 'accepted') return <Tag color="green">已验收</Tag>
|
||||
|
||||
// 份额级问题单:标记只影响该打手;已标记时按母单同一套方式处置。
|
||||
// 已验收份额的报酬/押金已结算,不能处置,因此也不提供标记入口。
|
||||
const disposableShare =
|
||||
share.status === 'joined' || share.status === 'submitted'
|
||||
const problemAction =
|
||||
canManageShareProblem && disposableShare ? (
|
||||
share.problemMarked ? (
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setDisposeShare(share)
|
||||
disposeShareForm.setFieldsValue({
|
||||
action: 'return_to_worker',
|
||||
note: '',
|
||||
})
|
||||
}}
|
||||
>
|
||||
处置问题单
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
icon={<WarningOutlined />}
|
||||
title="只标记该参与者的份额;母单状态与其他打手不受影响"
|
||||
onClick={() => {
|
||||
setProblemShare(share)
|
||||
problemShareForm.setFieldsValue({ note: '' })
|
||||
}}
|
||||
>
|
||||
标记问题单
|
||||
</Button>
|
||||
)
|
||||
) : null
|
||||
|
||||
return (
|
||||
<Space size={6}>
|
||||
<Space size={6} className="admin-share-actions">
|
||||
{share.status === 'submitted' ? (
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -3271,6 +3418,7 @@ export default function WorkOrdersPanel() {
|
||||
单人撤单
|
||||
</Button>
|
||||
) : null}
|
||||
{problemAction}
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
@@ -3315,6 +3463,93 @@ export default function WorkOrdersPanel() {
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`标记份额问题单 · ${
|
||||
problemShare?.worker?.displayName || problemShare?.worker?.username || ''
|
||||
}`}
|
||||
open={Boolean(problemShare)}
|
||||
okText="确认标记"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
onCancel={() => {
|
||||
setProblemShare(null)
|
||||
problemShareForm.resetFields()
|
||||
}}
|
||||
onOk={() => problemShareForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
{problemShare ? (
|
||||
<Form form={problemShareForm} layout="vertical" onFinish={submitProblemShare}>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message={`将标记该打手承接的 ${problemShare.quantity} 份拼单为问题单`}
|
||||
description="只有该份额持有人会在「我的订单」里看到问题单与备注;同工单其他打手、母单状态与份额状态都不受影响。整单问题单仍可在订单详情里操作。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item label="问题备注" name="note">
|
||||
<Input.TextArea rows={3} placeholder="选填,仅该份额持有人可见" maxLength={500} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`处置份额问题单 · ${
|
||||
disposeShare?.worker?.displayName || disposeShare?.worker?.username || ''
|
||||
}`}
|
||||
open={Boolean(disposeShare)}
|
||||
okText="确认处置"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
onCancel={() => {
|
||||
setDisposeShare(null)
|
||||
disposeShareForm.resetFields()
|
||||
}}
|
||||
onOk={() => disposeShareForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
{disposeShare ? (
|
||||
<Form
|
||||
form={disposeShareForm}
|
||||
layout="vertical"
|
||||
initialValues={{ action: 'return_to_worker', note: '' }}
|
||||
onFinish={submitShareDisposal}
|
||||
>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message={`只处置「${
|
||||
disposeShare.worker?.displayName || disposeShare.worker?.username || '该打手'
|
||||
}」的 ${disposeShare.quantity} 份份额`}
|
||||
description="处置方式与母单一致,但只作用于这一份:母单状态、其他打手都不受影响。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item
|
||||
label="处置方式"
|
||||
name="action"
|
||||
rules={[{ required: true, message: '请选择处置方式' }]}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'return_to_worker', label: '退回打手继续代练' },
|
||||
{ value: 'reopen', label: '释放押金并重新放回大厅' },
|
||||
{ value: 'cancel_release', label: '取消该份额并退还押金' },
|
||||
{ value: 'cancel_deduct', label: '取消该份额并扣除押金' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="处置备注"
|
||||
name="note"
|
||||
rules={[{ required: true, message: '请填写处置备注' }]}
|
||||
>
|
||||
<Input.TextArea rows={4} placeholder="如:验收图片与要求不符,退回继续代练" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`拼单撤单处理 · ${getDisplayOrderNo(cancelSharingOrder)}`}
|
||||
open={Boolean(cancelSharingOrder)}
|
||||
|
||||
@@ -258,6 +258,7 @@ export function formatSharingShareCancelledReason(reason: string) {
|
||||
timeout: '超时失效',
|
||||
admin: '后台撤单',
|
||||
worker_request: '打手退单',
|
||||
problem: '问题单处置',
|
||||
}
|
||||
return labels[reason] || reason
|
||||
}
|
||||
|
||||
@@ -87,6 +87,43 @@ export function WorkerOrderShareTable({
|
||||
return <Tag color={meta.color}>{meta.label}</Tag>
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '问题',
|
||||
width: 150,
|
||||
render: (_, share) => {
|
||||
if (!share.problemMarked) {
|
||||
return (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
—
|
||||
</Typography.Text>
|
||||
)
|
||||
}
|
||||
const note = String(share.problemNote || '').trim()
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<Tag
|
||||
color="red"
|
||||
title={
|
||||
share.problemMarkedAt
|
||||
? `标记时间:${formatDateTime(share.problemMarkedAt)}`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
问题单
|
||||
</Tag>
|
||||
{note ? (
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
ellipsis={{ tooltip: note }}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{note}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '截止时间',
|
||||
width: 170,
|
||||
@@ -171,7 +208,7 @@ export function WorkerOrderShareTable({
|
||||
dataSource={shares}
|
||||
columns={columns}
|
||||
className="worker-order-share-table"
|
||||
scroll={{ x: mobile ? 820 : 1080 }}
|
||||
scroll={{ x: mobile ? 970 : 1230 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,12 @@ export function getOrderFlagTags(order: WorkOrder) {
|
||||
}
|
||||
return flags
|
||||
}
|
||||
if (order.problemMarked) flags.push({ key: 'problem', label: '平台标记', color: 'red' })
|
||||
if (order.problemMarked)
|
||||
flags.push(
|
||||
order.shareProblemMarked
|
||||
? { key: 'share_problem', label: '份额问题', color: 'red' }
|
||||
: { key: 'problem', label: '平台标记', color: 'red' },
|
||||
)
|
||||
if (order.pendingCancelRequest)
|
||||
flags.push({ key: 'cancel', label: '退单审核中', color: 'orange' })
|
||||
if (order.pendingFeedback) flags.push({ key: 'feedback', label: '反馈处理中', color: 'orange' })
|
||||
|
||||
@@ -892,6 +892,29 @@ export function cancelAdminWorkOrderShare(
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/cancel`, payload)
|
||||
}
|
||||
|
||||
/** 按份额标记问题单:只影响该份额持有人,母单状态不变。备注选填。 */
|
||||
export function markAdminWorkOrderShareProblem(workOrderId: number, shareId: number, note = '') {
|
||||
return apiPost<{ order: WorkOrder; share: WorkOrderShare }>(
|
||||
`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/problem`,
|
||||
{ note },
|
||||
)
|
||||
}
|
||||
|
||||
/** 处置份额级问题单:方式与母单处置一致,但只作用于该份额。 */
|
||||
export function disposeAdminWorkOrderShareProblem(
|
||||
workOrderId: number,
|
||||
shareId: number,
|
||||
payload: {
|
||||
action: 'return_to_worker' | 'reopen' | 'cancel_release' | 'cancel_deduct'
|
||||
note: string
|
||||
},
|
||||
) {
|
||||
return apiPost<{ order: WorkOrder; share: WorkOrderShare; depositAmount: number }>(
|
||||
`/api/v1/admin/worker-platform/orders/${workOrderId}/shares/${shareId}/problem/dispose`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function cancelAdminWorkOrderSharing(
|
||||
workOrderId: number,
|
||||
payload: {
|
||||
|
||||
@@ -1607,3 +1607,31 @@
|
||||
grid-column: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 拼单明细:操作按钮保持一行 */
|
||||
.admin-share-actions {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 拼单明细:问题单行强调、已撤单行弱化 */
|
||||
.admin-share-row-problem > td {
|
||||
background: #fff1f0 !important;
|
||||
}
|
||||
|
||||
.admin-share-row-problem > td:first-child {
|
||||
box-shadow: inset 3px 0 0 #ff4d4f;
|
||||
}
|
||||
|
||||
.admin-share-row-problem:hover > td {
|
||||
background: #ffe7e6 !important;
|
||||
}
|
||||
|
||||
.admin-share-row-cancelled > td {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.admin-share-row-cancelled:hover > td {
|
||||
background: #f5f5f5 !important;
|
||||
}
|
||||
|
||||
@@ -545,6 +545,11 @@ export type WorkOrderShare = {
|
||||
timeoutMinutes?: number
|
||||
timeoutPolicy?: string
|
||||
cancelledReason?: string
|
||||
/** 份额级问题单:只影响该份额持有人,母单状态不变 */
|
||||
problemMarked?: boolean
|
||||
problemNote?: string
|
||||
problemMarkedAt?: string | null
|
||||
problemMarkedBy?: string
|
||||
worker: null | {
|
||||
workerId: number
|
||||
username: string
|
||||
@@ -651,6 +656,8 @@ export type WorkOrder = {
|
||||
problemNote: string
|
||||
/** 问题单分组来源角标:平台标记 / 退单审核中 / 反馈处理中 / 资料已更新。 */
|
||||
problemMarked?: boolean
|
||||
/** 份额级问题单(后台按份额标记,只有本人可见;母单状态不变) */
|
||||
shareProblemMarked?: boolean
|
||||
pendingCancelRequest?: boolean
|
||||
pendingFeedback?: boolean
|
||||
materialUpdated?: boolean
|
||||
|
||||
Reference in New Issue
Block a user