feat: 客服可修改工单,商品名双击编辑,订单列显示来源店铺

- 工单编辑接口放开 support 角色,客服可双击改发单金额/押金及商品名
- 订单列商品名支持双击原地修改(单击看详情不变)
- 订单列新增来源/店铺展示;Mock 工单补充店铺信息并回填存量数据
This commit is contained in:
yml2213
2026-08-15 10:07:32 +08:00
parent f078c75378
commit ecdbacd745
3 changed files with 120 additions and 17 deletions
@@ -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,