feat: 客服可修改工单,商品名双击编辑,订单列显示来源店铺
- 工单编辑接口放开 support 角色,客服可双击改发单金额/押金及商品名 - 订单列商品名支持双击原地修改(单击看详情不变) - 订单列新增来源/店铺展示;Mock 工单补充店铺信息并回填存量数据
This commit is contained in:
@@ -325,7 +325,7 @@ router.post(
|
||||
|
||||
router.put(
|
||||
'/worker-platform/orders/:workOrderId',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler(
|
||||
(req) => updateAdminWorkOrder(String(req.params.workOrderId || ''), req.body || {}),
|
||||
{
|
||||
|
||||
@@ -834,6 +834,9 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
||||
orderAmountFen: paymentFen,
|
||||
syncSource: 'mock',
|
||||
mock: true,
|
||||
provider: 'mock',
|
||||
platform: 'mock',
|
||||
shopName: '模拟店铺',
|
||||
},
|
||||
}
|
||||
const workOrder = await createWorkOrder({
|
||||
|
||||
@@ -483,24 +483,38 @@ export default function WorkOrdersPanel() {
|
||||
{
|
||||
title: '订单',
|
||||
minWidth: 300,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Link onClick={() => openDetailDrawer(row)}>
|
||||
{row.productName || row.workOrderNo}
|
||||
</Typography.Link>
|
||||
<Space size={[6, 6]} wrap>
|
||||
{row.categoryName ? <Tag>{row.categoryName}</Tag> : null}
|
||||
{row.orderId ? (
|
||||
<Typography.Text type="secondary">
|
||||
源订单 #{row.orderId}
|
||||
render: (_, row) => {
|
||||
const source = asRecord(row.material?.source)
|
||||
const shopName = String(source.shopName || '').trim()
|
||||
const provider = String(source.provider || '').trim()
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<ProductNameEditableCell
|
||||
order={row}
|
||||
onOpenDetail={() => openDetailDrawer(row)}
|
||||
onSaved={refreshAll}
|
||||
/>
|
||||
<Space size={[6, 6]} wrap>
|
||||
{row.categoryName ? <Tag>{row.categoryName}</Tag> : null}
|
||||
{row.orderId ? (
|
||||
<Typography.Text type="secondary">
|
||||
源订单 #{row.orderId}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</Space>
|
||||
{shopName || provider ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{provider ? `来源:${provider}` : ''}
|
||||
{provider && shopName ? ' · ' : ''}
|
||||
{shopName ? `店铺:${shopName}` : ''}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</Space>
|
||||
<Typography.Text type="secondary">
|
||||
{row.platformOrderId} · {row.workOrderNo}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
<Typography.Text type="secondary">
|
||||
{row.platformOrderId} · {row.workOrderNo}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '用户付款金额',
|
||||
@@ -1438,6 +1452,92 @@ function getUserPaymentFen(order: WorkOrder): number | null {
|
||||
return Number.isFinite(paymentFen) ? paymentFen : null
|
||||
}
|
||||
|
||||
/** 商品名:单击看详情,双击原地修改(仅未接走工单) */
|
||||
function ProductNameEditableCell({
|
||||
order,
|
||||
onOpenDetail,
|
||||
onSaved,
|
||||
}: {
|
||||
order: WorkOrder
|
||||
onOpenDetail: () => void
|
||||
onSaved: () => Promise<void> | void
|
||||
}) {
|
||||
const { message } = App.useApp()
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
const savingRef = useRef(false)
|
||||
const clickTimerRef = useRef<number | null>(null)
|
||||
|
||||
const editable =
|
||||
['pending_material', 'unassigned', 'open'].includes(order.status) && !order.worker
|
||||
|
||||
function handleClick() {
|
||||
if (clickTimerRef.current) window.clearTimeout(clickTimerRef.current)
|
||||
clickTimerRef.current = window.setTimeout(() => onOpenDetail(), 260)
|
||||
}
|
||||
|
||||
function handleDoubleClick() {
|
||||
if (clickTimerRef.current) {
|
||||
window.clearTimeout(clickTimerRef.current)
|
||||
clickTimerRef.current = null
|
||||
}
|
||||
if (!editable) return
|
||||
setDraft(order.productName || order.workOrderNo)
|
||||
setEditing(true)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (savingRef.current) return
|
||||
const next = draft.trim()
|
||||
if (!next) {
|
||||
message.error('商品名不能为空')
|
||||
return
|
||||
}
|
||||
if (next === order.productName) {
|
||||
setEditing(false)
|
||||
return
|
||||
}
|
||||
savingRef.current = true
|
||||
try {
|
||||
await updateAdminWorkOrder(order.workOrderId, { productName: next })
|
||||
message.success('商品名已更新')
|
||||
setEditing(false)
|
||||
await onSaved()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '保存失败')
|
||||
} finally {
|
||||
savingRef.current = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!editing) {
|
||||
return (
|
||||
<Typography.Link
|
||||
title={editable ? '双击修改商品名' : undefined}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
{order.productName || order.workOrderNo}
|
||||
</Typography.Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
autoFocus
|
||||
size="small"
|
||||
style={{ width: 200 }}
|
||||
value={draft}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
onPressEnter={() => void save()}
|
||||
onBlur={() => void save()}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') setEditing(false)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AmountEditableCell({
|
||||
order,
|
||||
field,
|
||||
|
||||
Reference in New Issue
Block a user