feat(consignment): 新增和平精英寄卖库存管理、定向收集审核与工单智能匹配履约
This commit is contained in:
@@ -11,6 +11,8 @@ import AfterSalesPanel, { type AfterSalesPanelHandle } from './panels/AfterSales
|
||||
import AnnouncementPanel from './panels/AnnouncementPanel'
|
||||
import CancelRequestsPanel from './panels/CancelRequestsPanel'
|
||||
import CategoriesPanel from './panels/CategoriesPanel'
|
||||
import ConsignmentInventoryPanel from './panels/ConsignmentInventoryPanel'
|
||||
import ConsignmentSubmissionsPanel from './panels/ConsignmentSubmissionsPanel'
|
||||
import FeedbacksPanel from './panels/FeedbacksPanel'
|
||||
import FinancePanel from './panels/FinancePanel'
|
||||
import LeaderboardPanel from './panels/LeaderboardPanel'
|
||||
@@ -94,6 +96,16 @@ export default function AdminWorkerPlatformPage() {
|
||||
: isSupport
|
||||
? [
|
||||
{ key: 'orders', label: '接单工单', children: <WorkOrdersPanel /> },
|
||||
{
|
||||
key: 'consignment-inventory',
|
||||
label: '寄售仓库',
|
||||
children: <ConsignmentInventoryPanel />,
|
||||
},
|
||||
{
|
||||
key: 'consignment-submissions',
|
||||
label: '寄卖收集与审核',
|
||||
children: <ConsignmentSubmissionsPanel />,
|
||||
},
|
||||
{
|
||||
key: 'cancel-requests',
|
||||
label: '打手退单审核',
|
||||
@@ -104,6 +116,16 @@ export default function AdminWorkerPlatformPage() {
|
||||
]
|
||||
: [
|
||||
{ key: 'orders', label: '接单工单', children: <WorkOrdersPanel /> },
|
||||
{
|
||||
key: 'consignment-inventory',
|
||||
label: '寄售仓库',
|
||||
children: <ConsignmentInventoryPanel />,
|
||||
},
|
||||
{
|
||||
key: 'consignment-submissions',
|
||||
label: '寄卖收集与审核',
|
||||
children: <ConsignmentSubmissionsPanel />,
|
||||
},
|
||||
{
|
||||
key: 'cancel-requests',
|
||||
label: '打手退单审核',
|
||||
@@ -142,6 +164,8 @@ const ACTIVE_TAB_STORAGE_KEY = 'admin-worker-platform-tab'
|
||||
function loadActiveTabPreference(): string {
|
||||
const saved = localStorage.getItem(ACTIVE_TAB_STORAGE_KEY)
|
||||
return saved === 'orders' ||
|
||||
saved === 'consignment-inventory' ||
|
||||
saved === 'consignment-submissions' ||
|
||||
saved === 'leaderboard' ||
|
||||
saved === 'cancel-requests' ||
|
||||
saved === 'feedbacks' ||
|
||||
@@ -165,6 +189,8 @@ function loadActiveTab(tab: string | null): string {
|
||||
function isWorkerPlatformTab(tab: string | null): tab is string {
|
||||
return [
|
||||
'orders',
|
||||
'consignment-inventory',
|
||||
'consignment-submissions',
|
||||
'leaderboard',
|
||||
'cancel-requests',
|
||||
'feedbacks',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,947 @@
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
CopyOutlined,
|
||||
EyeOutlined,
|
||||
LinkOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Form,
|
||||
Image,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Radio,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import { useState } from 'react'
|
||||
|
||||
import {
|
||||
approveAdminConsignmentSubmission,
|
||||
createAdminConsignmentLink,
|
||||
fetchAdminConsignmentLinks,
|
||||
fetchAdminConsignmentSubmissions,
|
||||
rejectAdminConsignmentSubmission,
|
||||
updateAdminConsignmentLink,
|
||||
} from '@/services/admin/consignment'
|
||||
import type { ConsignmentCollectLink, ConsignmentSubmission } from '@/types/consignment'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
export default function ConsignmentSubmissionsPanel() {
|
||||
const { message } = App.useApp()
|
||||
const [activeSubTab, setActiveSubTab] = useState<'submissions' | 'links'>('submissions')
|
||||
|
||||
// 提报审核状态
|
||||
const [submissionStatus, setSubmissionStatus] = useState<string>('pending_review')
|
||||
const [submissionKeyword, setSubmissionKeyword] = useState<string>('')
|
||||
const [submissionPage, setSubmissionPage] = useState<number>(1)
|
||||
const [submissionPageSize, setSubmissionPageSize] = useState<number>(20)
|
||||
|
||||
// 弹窗状态
|
||||
const [createLinkModalOpen, setCreateLinkModalOpen] = useState(false)
|
||||
const [createdLinkUrl, setCreatedLinkUrl] = useState<string>('')
|
||||
const [approveModalItem, setApproveModalItem] = useState<ConsignmentSubmission | null>(null)
|
||||
const [rejectModalItem, setRejectModalItem] = useState<ConsignmentSubmission | null>(null)
|
||||
const [viewingSubmission, setViewingSubmission] = useState<ConsignmentSubmission | null>(null)
|
||||
|
||||
const [rejectNote, setRejectNote] = useState('')
|
||||
const [linkForm] = Form.useForm()
|
||||
const [approveForm] = Form.useForm()
|
||||
|
||||
// 查询提报列表
|
||||
const submissionsQuery = useQuery({
|
||||
queryKey: [
|
||||
'admin-consignment-submissions',
|
||||
submissionStatus,
|
||||
submissionKeyword,
|
||||
submissionPage,
|
||||
submissionPageSize,
|
||||
],
|
||||
queryFn: () =>
|
||||
fetchAdminConsignmentSubmissions({
|
||||
status: submissionStatus === 'all' ? undefined : submissionStatus,
|
||||
keyword: submissionKeyword || undefined,
|
||||
page: submissionPage,
|
||||
pageSize: submissionPageSize,
|
||||
}),
|
||||
})
|
||||
|
||||
// 查询链接列表
|
||||
const linksQuery = useQuery({
|
||||
queryKey: ['admin-consignment-links'],
|
||||
queryFn: () => fetchAdminConsignmentLinks(),
|
||||
})
|
||||
|
||||
const submissions = submissionsQuery.data?.data.items || []
|
||||
const submissionTotal = submissionsQuery.data?.data.total || 0
|
||||
const links = Array.isArray(linksQuery.data?.data)
|
||||
? linksQuery.data?.data
|
||||
: linksQuery.data?.data?.items || []
|
||||
|
||||
// 复制收集公开链接
|
||||
function copyPublicLink(token: string) {
|
||||
const origin = window.location.origin
|
||||
const url = `${origin}/#/consignment-collect/${token}`
|
||||
navigator.clipboard.writeText(url)
|
||||
message.success('收集链接已复制到剪贴板,可直接发给客户!')
|
||||
}
|
||||
|
||||
// 切换链接启用/关闭
|
||||
async function handleToggleLink(record: ConsignmentCollectLink, enabled: boolean) {
|
||||
try {
|
||||
await updateAdminConsignmentLink(record.id, { enabled })
|
||||
message.success(enabled ? '链接已开启' : '链接已关闭')
|
||||
linksQuery.refetch()
|
||||
} catch (err: any) {
|
||||
message.error(err.message || '更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 创建收集链接
|
||||
async function handleCreateLinkSubmit() {
|
||||
try {
|
||||
const values = await linkForm.validateFields()
|
||||
const presetPriceFen = Math.round(Number(values.presetPrice || 0) * 100)
|
||||
const briStr = values.brillianceCount > 0 ? `${values.brillianceCount}璀璨` : ''
|
||||
const defaultTitle = `${values.system}${values.serverZone}${values.series}${values.diamondLevel}钻${briStr} 寄卖征集`
|
||||
|
||||
const res = await createAdminConsignmentLink({
|
||||
title: values.title || defaultTitle,
|
||||
system: values.system,
|
||||
serverZone: values.serverZone,
|
||||
series: values.series,
|
||||
diamondLevel: Number(values.diamondLevel || 1),
|
||||
brillianceCount: Number(values.brillianceCount || 0),
|
||||
presetPriceFen,
|
||||
buyerName: values.buyerName,
|
||||
inboundBatch: values.inboundBatch,
|
||||
isMultiUse: values.isMultiUse !== false,
|
||||
})
|
||||
|
||||
const token = res.data?.token || ''
|
||||
const url = `${window.location.origin}/#/consignment-collect/${token}`
|
||||
setCreatedLinkUrl(url)
|
||||
try {
|
||||
navigator.clipboard.writeText(url)
|
||||
} catch {}
|
||||
|
||||
message.success('收集链接生成成功!')
|
||||
setCreateLinkModalOpen(false)
|
||||
linkForm.resetFields()
|
||||
await linksQuery.refetch()
|
||||
} catch (err: any) {
|
||||
if (err.errorFields) return
|
||||
message.error(err.message || '创建收集链接失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 审核通过并入库
|
||||
async function handleApproveSubmit() {
|
||||
if (!approveModalItem) return
|
||||
try {
|
||||
const values = await approveForm.validateFields()
|
||||
const costPriceFen = Math.round(Number(values.costPrice || 0) * 100)
|
||||
|
||||
await approveAdminConsignmentSubmission(approveModalItem.id, {
|
||||
costPriceFen,
|
||||
buyerName: values.buyerName,
|
||||
shopName: values.shopName,
|
||||
inboundBatch: values.inboundBatch,
|
||||
itemName: values.itemName,
|
||||
system: values.system,
|
||||
serverZone: values.serverZone,
|
||||
series: values.series,
|
||||
diamondLevel: Number(values.diamondLevel || 1),
|
||||
brillianceCount: Number(values.brillianceCount || 0),
|
||||
sellerContact: values.sellerContact,
|
||||
sellerGameId: values.sellerGameId,
|
||||
sellerPaymentInfo: values.sellerPaymentInfo,
|
||||
sellerPhone: values.sellerPhone,
|
||||
reviewNote: values.reviewNote,
|
||||
})
|
||||
|
||||
message.success('审核通过!已正式存入寄售库存待匹配!')
|
||||
setApproveModalItem(null)
|
||||
submissionsQuery.refetch()
|
||||
} catch (err: any) {
|
||||
if (err.errorFields) return
|
||||
message.error(err.message || '审核入库失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 驳回提报
|
||||
async function handleRejectConfirm() {
|
||||
if (!rejectModalItem) return
|
||||
try {
|
||||
await rejectAdminConsignmentSubmission(rejectModalItem.id, { reviewNote: rejectNote })
|
||||
message.success('已驳回该提报申请')
|
||||
setRejectModalItem(null)
|
||||
setRejectNote('')
|
||||
submissionsQuery.refetch()
|
||||
} catch (err: any) {
|
||||
message.error(err.message || '驳回失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 提报列表列定义
|
||||
const submissionColumns: TableColumnsType<ConsignmentSubmission> = [
|
||||
{
|
||||
title: '提报单号',
|
||||
key: 'submissionNo',
|
||||
width: 170,
|
||||
render: (_, record) => (
|
||||
<div>
|
||||
<Text strong>{record.submissionNo}</Text>
|
||||
{record.linkTitle ? (
|
||||
<div style={{ fontSize: 11, color: '#8c8c8c' }}>来自: {record.linkTitle}</div>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '提报物品规格',
|
||||
key: 'specs',
|
||||
width: 210,
|
||||
render: (_, record) => (
|
||||
<Space direction="vertical" size={2}>
|
||||
<div style={{ fontWeight: 600 }}>{record.itemName}</div>
|
||||
<Space size={4} wrap>
|
||||
<Tag color={record.system === '苹果' ? 'geekblue' : 'green'}>{record.system}</Tag>
|
||||
<Tag color="cyan">{record.serverZone}</Tag>
|
||||
<Tag color="orange">{record.series}</Tag>
|
||||
<Tag color="gold">{record.diamondLevel} 钻</Tag>
|
||||
{record.brillianceCount > 0 ? (
|
||||
<Tag color="magenta">{record.brillianceCount} 璀璨</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '卖家报价',
|
||||
key: 'price',
|
||||
width: 110,
|
||||
render: (_, record) => (
|
||||
<Text strong style={{ color: '#cf1322', fontSize: 15 }}>
|
||||
¥{(record.askingPriceFen / 100).toFixed(2)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '卖家联系方式',
|
||||
key: 'contact',
|
||||
width: 180,
|
||||
render: (_, record) => (
|
||||
<div style={{ fontSize: 12 }}>
|
||||
<div>联系: {record.contactInfo}</div>
|
||||
{record.sellerGameId ? <div>游戏ID: {record.sellerGameId}</div> : null}
|
||||
{record.sellerPhone ? <div>电话: {record.sellerPhone}</div> : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '凭据截图',
|
||||
key: 'screens',
|
||||
width: 110,
|
||||
render: (_, record) => {
|
||||
const screens = Array.isArray(record.screenshotsJson) ? record.screenshotsJson : []
|
||||
if (screens.length === 0) return <Text type="secondary">无截图</Text>
|
||||
return (
|
||||
<Image.PreviewGroup>
|
||||
<Image
|
||||
src={screens[0]}
|
||||
width={48}
|
||||
height={48}
|
||||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
</Image.PreviewGroup>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (_, record) => {
|
||||
if (record.status === 'pending_review') return <Tag color="gold">待客服审核</Tag>
|
||||
if (record.status === 'approved') return <Tag color="green">已入库</Tag>
|
||||
return <Tag color="red">已驳回</Tag>
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '提报时间',
|
||||
dataIndex: 'createdAt',
|
||||
width: 150,
|
||||
render: (v) => <Text style={{ fontSize: 12 }}>{v}</Text>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 160,
|
||||
render: (_, record) => (
|
||||
<Space size={8}>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => setViewingSubmission(record)}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
{record.status === 'pending_review' ? (
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setApproveModalItem(record)
|
||||
approveForm.setFieldsValue({
|
||||
...record,
|
||||
costPrice: record.askingPriceFen / 100,
|
||||
sellerContact: record.contactInfo,
|
||||
buyerName: '客服',
|
||||
inboundBatch: new Date().toISOString().slice(0, 7),
|
||||
})
|
||||
}}
|
||||
>
|
||||
审核入库
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
type="text"
|
||||
onClick={() => {
|
||||
setRejectModalItem(record)
|
||||
setRejectNote('')
|
||||
}}
|
||||
>
|
||||
驳回
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
// 链接列表列定义
|
||||
const linkColumns: TableColumnsType<ConsignmentCollectLink> = [
|
||||
{
|
||||
title: '收集标题 / 规格',
|
||||
key: 'title',
|
||||
width: 280,
|
||||
render: (_, record) => {
|
||||
const fullUrl = `${window.location.origin}/#/consignment-collect/${record.token}`
|
||||
return (
|
||||
<div>
|
||||
<div style={{ fontWeight: 600 }}>{record.title}</div>
|
||||
<Space size={4} wrap style={{ marginTop: 4 }}>
|
||||
<Tag color={record.system === '苹果' ? 'geekblue' : 'green'}>
|
||||
{record.system || '通用'}
|
||||
</Tag>
|
||||
<Tag color="cyan">{record.serverZone || '全区'}</Tag>
|
||||
<Tag color="orange">{record.series || '通用'}</Tag>
|
||||
<Tag color="gold">{record.diamondLevel || 1} 钻</Tag>
|
||||
{record.brillianceCount > 0 ? (
|
||||
<Tag color="magenta">{record.brillianceCount} 璀璨</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
<div style={{ marginTop: 6 }}>
|
||||
<Text
|
||||
copyable={{ text: fullUrl }}
|
||||
style={{ fontSize: 11, color: '#1890ff', wordBreak: 'break-all' }}
|
||||
>
|
||||
{fullUrl}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '指导参考价',
|
||||
key: 'price',
|
||||
width: 110,
|
||||
render: (_, record) => (
|
||||
<Text strong style={{ color: '#cf1322' }}>
|
||||
¥{(record.presetPriceFen / 100).toFixed(2)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '创建人/批次',
|
||||
key: 'creator',
|
||||
width: 140,
|
||||
render: (_, record) => (
|
||||
<div>
|
||||
<div>{record.createdBy || '-'}</div>
|
||||
{record.inboundBatch ? (
|
||||
<div style={{ fontSize: 11, color: '#8c8c8c' }}>批次: {record.inboundBatch}</div>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '提报数量',
|
||||
key: 'count',
|
||||
width: 100,
|
||||
render: (_, record) => (
|
||||
<Badge
|
||||
count={record.submissionCount || 0}
|
||||
showZero
|
||||
style={{ backgroundColor: (record.submissionCount || 0) > 0 ? '#52c41a' : '#d9d9d9' }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态开关',
|
||||
key: 'enabled',
|
||||
width: 100,
|
||||
render: (_, record) => (
|
||||
<Switch
|
||||
checked={record.enabled}
|
||||
checkedChildren="开启"
|
||||
unCheckedChildren="关闭"
|
||||
onChange={(checked) => handleToggleLink(record, checked)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 180,
|
||||
render: (_, record) => (
|
||||
<Space size={8}>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => copyPublicLink(record.token)}
|
||||
>
|
||||
复制公开链接
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<Tabs
|
||||
activeKey={activeSubTab}
|
||||
onChange={(k) => setActiveSubTab(k as any)}
|
||||
items={[
|
||||
{
|
||||
key: 'submissions',
|
||||
label: (
|
||||
<span>
|
||||
玩家提报审核{' '}
|
||||
{submissions.filter((s) => s.status === 'pending_review').length > 0 ? (
|
||||
<Badge
|
||||
count={submissions.filter((s) => s.status === 'pending_review').length}
|
||||
offset={[6, -2]}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{/* 过滤条 */}
|
||||
<Card size="small">
|
||||
<Space wrap size={12}>
|
||||
<Radio.Group
|
||||
value={submissionStatus}
|
||||
onChange={(e) => {
|
||||
setSubmissionStatus(e.target.value)
|
||||
setSubmissionPage(1)
|
||||
}}
|
||||
buttonStyle="solid"
|
||||
>
|
||||
<Radio.Button value="pending_review">待客服审核</Radio.Button>
|
||||
<Radio.Button value="approved">已审核入库</Radio.Button>
|
||||
<Radio.Button value="rejected">已驳回</Radio.Button>
|
||||
<Radio.Button value="all">全部</Radio.Button>
|
||||
</Radio.Group>
|
||||
|
||||
<Input.Search
|
||||
placeholder="搜索提报单号/微信号/游戏ID"
|
||||
allowClear
|
||||
style={{ width: 240 }}
|
||||
onSearch={(v) => {
|
||||
setSubmissionKeyword(v)
|
||||
setSubmissionPage(1)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button icon={<ReloadOutlined />} onClick={() => submissionsQuery.refetch()}>
|
||||
刷新
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 提报审核表格 */}
|
||||
<Card size="small">
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={submissionColumns}
|
||||
dataSource={submissions}
|
||||
loading={submissionsQuery.isLoading}
|
||||
pagination={{
|
||||
current: submissionPage,
|
||||
pageSize: submissionPageSize,
|
||||
total: submissionTotal,
|
||||
showSizeChanger: true,
|
||||
showTotal: (t) => `共 ${t} 条提报记录`,
|
||||
onChange: (p, ps) => {
|
||||
setSubmissionPage(p)
|
||||
setSubmissionPageSize(ps)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'links',
|
||||
label: '收集链接生成与管理',
|
||||
children: (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<Card size="small">
|
||||
<Space wrap size={12}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
linkForm.resetFields()
|
||||
linkForm.setFieldsValue({
|
||||
system: '苹果',
|
||||
serverZone: '微信区',
|
||||
series: '暗潮',
|
||||
diamondLevel: 1,
|
||||
brillianceCount: 0,
|
||||
isMultiUse: true,
|
||||
buyerName: '客服',
|
||||
})
|
||||
setCreateLinkModalOpen(true)
|
||||
}}
|
||||
>
|
||||
生成定向收集链接
|
||||
</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => linksQuery.refetch()}>
|
||||
刷新
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card size="small">
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={linkColumns}
|
||||
dataSource={links}
|
||||
loading={linksQuery.isLoading}
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* 生成收集链接弹窗 */}
|
||||
<Modal
|
||||
title="生成寄卖线索收集链接"
|
||||
open={createLinkModalOpen}
|
||||
width={600}
|
||||
onCancel={() => setCreateLinkModalOpen(false)}
|
||||
onOk={handleCreateLinkSubmit}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={linkForm} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item name="system" label="指定系统" rules={[{ required: true }]}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: '苹果 (iOS)', value: '苹果' },
|
||||
{ label: '安卓 (Android)', value: '安卓' },
|
||||
{ label: '通用', value: '通用' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="serverZone" label="指定区服" rules={[{ required: true }]}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: '微信区', value: '微信区' },
|
||||
{ label: 'QQ区', value: 'QQ区' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="series" label="枪系/系列" rules={[{ required: true }]}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: '暗潮', value: '暗潮' },
|
||||
{ label: '怒嚎', value: '怒嚎' },
|
||||
{ label: '血宴', value: '血宴' },
|
||||
{ label: '矩阵', value: '矩阵' },
|
||||
{ label: '其他', value: '其他' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item name="diamondLevel" label="钻石等级" rules={[{ required: true }]}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: '1 钻', value: 1 },
|
||||
{ label: '2 钻', value: 2 },
|
||||
{ label: '3 钻', value: 3 },
|
||||
{ label: '4 钻', value: 4 },
|
||||
{ label: '5 钻', value: 5 },
|
||||
{ label: '6 钻', value: 6 },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="brillianceCount" label="璀璨数">
|
||||
<InputNumber min={0} max={6} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="presetPrice" label="指导收购价 (元)">
|
||||
<InputNumber min={0} style={{ width: '100%' }} placeholder="如 80" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="链接标题 (留空则根据规格自动生成)"
|
||||
tooltip="玩家打开链接时看到的标题"
|
||||
>
|
||||
<Input placeholder="如:【高价收】苹果微信暗潮1钻 极速审核" />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="buyerName" label="默认收购人">
|
||||
<Input placeholder="客服姓名" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="inboundBatch" label="归属批次">
|
||||
<Input placeholder="如 202609-01" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="isMultiUse" label="允许多次提报" valuePropName="checked">
|
||||
<Switch defaultChecked checkedChildren="多人可填" unCheckedChildren="单次失效" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 审核入库弹窗 */}
|
||||
<Modal
|
||||
title="审核寄卖申请并正式入库"
|
||||
open={Boolean(approveModalItem)}
|
||||
width={680}
|
||||
onCancel={() => setApproveModalItem(null)}
|
||||
onOk={handleApproveSubmit}
|
||||
okText="审核通过并正式入库"
|
||||
okType="primary"
|
||||
destroyOnClose
|
||||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Text type="secondary">
|
||||
提报人联系方式:<strong>{approveModalItem?.contactInfo}</strong> | 游戏ID:
|
||||
<strong>{approveModalItem?.sellerGameId || '-'}</strong> | 卖家要价:
|
||||
<strong style={{ color: '#cf1322' }}>
|
||||
¥{((approveModalItem?.askingPriceFen || 0) / 100).toFixed(2)}
|
||||
</strong>
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Form form={approveForm} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={14}>
|
||||
<Form.Item
|
||||
name="itemName"
|
||||
label="正式入库物品名"
|
||||
rules={[{ required: true, message: '请输入入库物品名' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
name="costPrice"
|
||||
label="实际收购成本 (元)"
|
||||
rules={[{ required: true, message: '请输入实际收购成本' }]}
|
||||
>
|
||||
<InputNumber min={0} step={1} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.Item name="system" label="系统">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item name="serverZone" label="区服">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item name="series" label="系列">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item name="diamondLevel" label="钻石等级">
|
||||
<InputNumber min={1} max={6} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
name="buyerName"
|
||||
label="收购人 (客服)"
|
||||
rules={[{ required: true, message: '请填写收购人' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="shopName" label="关联店铺">
|
||||
<Input placeholder="店铺名" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="inboundBatch" label="收购批次">
|
||||
<Input placeholder="批次" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item name="sellerContact" label="卖家微信号">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="sellerPhone" label="卖家手机">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="sellerGameId" label="卖家游戏ID">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="sellerPaymentInfo" label="卖家收款信息">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="reviewNote" label="审核备注">
|
||||
<Input.TextArea rows={2} placeholder="审核核准说明" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 驳回弹窗 */}
|
||||
<Modal
|
||||
title="驳回寄卖提报"
|
||||
open={Boolean(rejectModalItem)}
|
||||
onCancel={() => setRejectModalItem(null)}
|
||||
onOk={handleRejectConfirm}
|
||||
okText="确认驳回"
|
||||
okType="danger"
|
||||
>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text>
|
||||
确认驳回 <strong>[{rejectModalItem?.submissionNo}]</strong> 的寄卖申请吗?
|
||||
</Text>
|
||||
</div>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="驳回原因说明">
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
value={rejectNote}
|
||||
onChange={(e) => setRejectNote(e.target.value)}
|
||||
placeholder="如:要价过高、物品规格不符、账号存在实名绑定限制等"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 提报详情查看弹窗 */}
|
||||
<Modal
|
||||
title={`提报详情 [${viewingSubmission?.submissionNo}]`}
|
||||
open={Boolean(viewingSubmission)}
|
||||
width={680}
|
||||
footer={[
|
||||
<Button key="close" onClick={() => setViewingSubmission(null)}>
|
||||
关闭
|
||||
</Button>,
|
||||
]}
|
||||
onCancel={() => setViewingSubmission(null)}
|
||||
>
|
||||
{viewingSubmission ? (
|
||||
<Descriptions bordered size="small" column={2}>
|
||||
<Descriptions.Item label="提报单号" span={2}>
|
||||
<Text copyable>{viewingSubmission.submissionNo}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="物品名称" span={2}>
|
||||
{viewingSubmission.itemName}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="规格">
|
||||
{viewingSubmission.system} / {viewingSubmission.serverZone} /{' '}
|
||||
{viewingSubmission.series} / {viewingSubmission.diamondLevel}钻 (
|
||||
{viewingSubmission.brillianceCount}璀璨)
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="卖家报价">
|
||||
<Text strong style={{ color: '#cf1322' }}>
|
||||
¥{(viewingSubmission.askingPriceFen / 100).toFixed(2)}
|
||||
</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="卖家微信号">
|
||||
<Text copyable>{viewingSubmission.contactInfo}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="卖家电话">
|
||||
<Text copyable>{viewingSubmission.sellerPhone || '-'}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="卖家游戏ID" span={2}>
|
||||
<Text copyable>{viewingSubmission.sellerGameId || '-'}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="卖家收款方式" span={2}>
|
||||
<Text copyable>{viewingSubmission.sellerPaymentInfo || '-'}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="凭据截图" span={2}>
|
||||
{Array.isArray(viewingSubmission.screenshotsJson) &&
|
||||
viewingSubmission.screenshotsJson.length > 0 ? (
|
||||
<Image.PreviewGroup>
|
||||
<Space wrap>
|
||||
{viewingSubmission.screenshotsJson.map((src, i) => (
|
||||
<Image
|
||||
key={i}
|
||||
src={src}
|
||||
width={80}
|
||||
height={80}
|
||||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
</Image.PreviewGroup>
|
||||
) : (
|
||||
<Text type="secondary">无截图</Text>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="审核状态">
|
||||
{viewingSubmission.status === 'pending_review' ? (
|
||||
<Tag color="gold">待审核</Tag>
|
||||
) : viewingSubmission.status === 'approved' ? (
|
||||
<Tag color="green">已入库</Tag>
|
||||
) : (
|
||||
<Tag color="red">已驳回</Tag>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="审核人">
|
||||
{viewingSubmission.reviewerName || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="审核备注" span={2}>
|
||||
{viewingSubmission.reviewNote || '-'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
{/* 链接生成成功提示弹窗 */}
|
||||
<Modal
|
||||
title="收集链接生成成功"
|
||||
open={Boolean(createdLinkUrl)}
|
||||
onCancel={() => setCreatedLinkUrl('')}
|
||||
footer={[
|
||||
<Button
|
||||
key="copy"
|
||||
type="primary"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => {
|
||||
navigator.clipboard?.writeText(createdLinkUrl)
|
||||
message.success('链接已复制到剪贴板!')
|
||||
}}
|
||||
>
|
||||
复制公开链接
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setCreatedLinkUrl('')}>
|
||||
完成
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Alert
|
||||
type="success"
|
||||
showIcon
|
||||
message="定向收集链接已就绪"
|
||||
description="该链接已自动复制到剪贴板。您可以直接发送给玩家卖家,玩家免登录打开即可登记账号与装备信息并上传凭据截图。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Text strong>公开收集链接:</Text>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '12px 16px',
|
||||
background: '#f6ffed',
|
||||
border: '1px solid #b7eb8f',
|
||||
borderRadius: 6,
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
copyable={{ text: createdLinkUrl }}
|
||||
style={{ fontSize: 13, color: '#389e0d', fontWeight: 500 }}
|
||||
>
|
||||
{createdLinkUrl}
|
||||
</Text>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
UndoOutlined,
|
||||
UserAddOutlined,
|
||||
WarningOutlined,
|
||||
RollbackOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
@@ -72,6 +73,12 @@ import {
|
||||
updateAdminWorkOrderNote,
|
||||
updateAdminWorkOrderSharing,
|
||||
} from '@/services/admin'
|
||||
import {
|
||||
checkWorkOrderConsignmentMatches,
|
||||
fulfillWorkOrderWithConsignment,
|
||||
revokeWorkOrderConsignmentFulfillment,
|
||||
} from '@/services/admin/consignment'
|
||||
import type { WorkOrderInventoryMatchCheck } from '@/types/consignment'
|
||||
import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
|
||||
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
|
||||
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
|
||||
@@ -259,6 +266,35 @@ export default function WorkOrdersPanel() {
|
||||
unitReward?: number
|
||||
totalAmount?: number
|
||||
}>()
|
||||
|
||||
// 寄售库存匹配拦截弹窗状态
|
||||
const [consignmentInterceptModal, setConsignmentInterceptModal] = useState<{
|
||||
open: boolean
|
||||
order: WorkOrder | null
|
||||
matchData: WorkOrderInventoryMatchCheck | null
|
||||
submitting: boolean
|
||||
}>({
|
||||
open: false,
|
||||
order: null,
|
||||
matchData: null,
|
||||
submitting: false,
|
||||
})
|
||||
|
||||
// 寄售履约撤单弹窗状态
|
||||
const [revokeConsignmentModal, setRevokeConsignmentModal] = useState<{
|
||||
open: boolean
|
||||
order: WorkOrder | null
|
||||
action: 'reopen' | 'cancel_order' | 'archive_inventory'
|
||||
reason: string
|
||||
submitting: boolean
|
||||
}>({
|
||||
open: false,
|
||||
order: null,
|
||||
action: 'reopen',
|
||||
reason: '',
|
||||
submitting: false,
|
||||
})
|
||||
|
||||
// 仅单独筛选「待验收」时提供批量验收入口(多选状态下不启用勾选,避免误操作)
|
||||
const isPendingAcceptanceOnly = statuses.length === 1 && statuses[0] === 'pending_acceptance'
|
||||
const selectedWorkOrderStatuses = statuses.filter(
|
||||
@@ -652,6 +688,75 @@ export default function WorkOrdersPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
async function handlePublishClick(row: WorkOrder) {
|
||||
try {
|
||||
const matchRes = await checkWorkOrderConsignmentMatches(row.workOrderId)
|
||||
if (matchRes.data?.hasMatch && (matchRes.data.matches?.length || 0) > 0) {
|
||||
setConsignmentInterceptModal({
|
||||
open: true,
|
||||
order: row,
|
||||
matchData: matchRes.data,
|
||||
submitting: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// 检查匹配异常降级走正常发布
|
||||
}
|
||||
confirmPublishWorkOrder(row)
|
||||
}
|
||||
|
||||
async function handleFulfillWithConsignment(inventoryId: number) {
|
||||
if (!consignmentInterceptModal.order) return
|
||||
setConsignmentInterceptModal((prev) => ({ ...prev, submitting: true }))
|
||||
try {
|
||||
await fulfillWorkOrderWithConsignment({
|
||||
workOrderId: consignmentInterceptModal.order.workOrderId,
|
||||
inventoryId,
|
||||
})
|
||||
message.success('已使用内部寄售库存直接履约!工单已自动完成,无需打手接单佣金')
|
||||
setConsignmentInterceptModal({ open: false, order: null, matchData: null, submitting: false })
|
||||
await refreshAll()
|
||||
} catch (err: any) {
|
||||
message.error(err.message || '内部履约失败')
|
||||
setConsignmentInterceptModal((prev) => ({ ...prev, submitting: false }))
|
||||
}
|
||||
}
|
||||
|
||||
function openRevokeConsignmentModal(row: WorkOrder) {
|
||||
setRevokeConsignmentModal({
|
||||
open: true,
|
||||
order: row,
|
||||
action: 'reopen',
|
||||
reason: '',
|
||||
submitting: false,
|
||||
})
|
||||
}
|
||||
|
||||
async function handleRevokeConsignmentSubmit() {
|
||||
if (!revokeConsignmentModal.order) return
|
||||
setRevokeConsignmentModal((prev) => ({ ...prev, submitting: true }))
|
||||
try {
|
||||
await revokeWorkOrderConsignmentFulfillment({
|
||||
workOrderId: revokeConsignmentModal.order.workOrderId,
|
||||
action: revokeConsignmentModal.action,
|
||||
reason: revokeConsignmentModal.reason,
|
||||
})
|
||||
message.success('寄售履约已撤销!物品已退单回库,工单已更新')
|
||||
setRevokeConsignmentModal({
|
||||
open: false,
|
||||
order: null,
|
||||
action: 'reopen',
|
||||
reason: '',
|
||||
submitting: false,
|
||||
})
|
||||
await refreshAll()
|
||||
} catch (err: any) {
|
||||
message.error(err.message || '撤销履约失败')
|
||||
setRevokeConsignmentModal((prev) => ({ ...prev, submitting: false }))
|
||||
}
|
||||
}
|
||||
|
||||
function confirmBatchAcceptance() {
|
||||
const workOrderIds = selectedAcceptanceOrderIds
|
||||
if (workOrderIds.length === 0) {
|
||||
@@ -1018,6 +1123,12 @@ export default function WorkOrdersPanel() {
|
||||
{shopName ? `店铺:${shopName}` : ''}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{(row.material as any)?.consignment ? (
|
||||
<Tag color="purple" style={{ marginTop: 2, width: 'fit-content' }}>
|
||||
寄售履约: {(row.material as any).consignment.inventoryNo} (成本: ¥
|
||||
{(((row.material as any).consignment.costPriceFen || 0) / 100).toFixed(2)})
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -1249,7 +1360,7 @@ export default function WorkOrdersPanel() {
|
||||
title={
|
||||
Number(row.rewardAmount || 0) <= 0 ? '接单金额为 0,不能发布到大厅' : undefined
|
||||
}
|
||||
onClick={() => confirmPublishWorkOrder(row)}
|
||||
onClick={() => handlePublishClick(row)}
|
||||
>
|
||||
发布并核销
|
||||
</Button>
|
||||
@@ -1301,6 +1412,15 @@ export default function WorkOrdersPanel() {
|
||||
扣押金
|
||||
</Button>
|
||||
) : null}
|
||||
{(row.material as any)?.consignment ? (
|
||||
<Button
|
||||
icon={<RollbackOutlined />}
|
||||
danger
|
||||
onClick={() => openRevokeConsignmentModal(row)}
|
||||
>
|
||||
撤单/撤销寄售
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'accepted' ? (
|
||||
<Button
|
||||
icon={<WarningOutlined />}
|
||||
@@ -2618,6 +2738,249 @@ export default function WorkOrdersPanel() {
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
{/* 内部寄售库存匹配拦截弹窗 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<span>内部寄售库存匹配提醒</span>
|
||||
<Tag color="purple">和平精英内部仓</Tag>
|
||||
</Space>
|
||||
}
|
||||
open={consignmentInterceptModal.open}
|
||||
width={760}
|
||||
onCancel={() => {
|
||||
setConsignmentInterceptModal({
|
||||
open: false,
|
||||
order: null,
|
||||
matchData: null,
|
||||
submitting: false,
|
||||
})
|
||||
}}
|
||||
footer={[
|
||||
<Button
|
||||
key="ignore-and-publish"
|
||||
onClick={() => {
|
||||
const currentOrder = consignmentInterceptModal.order
|
||||
setConsignmentInterceptModal({
|
||||
open: false,
|
||||
order: null,
|
||||
matchData: null,
|
||||
submitting: false,
|
||||
})
|
||||
if (currentOrder) confirmPublishWorkOrder(currentOrder)
|
||||
}}
|
||||
>
|
||||
忽略内部库存,继续发布到打手抢单大厅
|
||||
</Button>,
|
||||
<Button
|
||||
key="close"
|
||||
onClick={() => {
|
||||
setConsignmentInterceptModal({
|
||||
open: false,
|
||||
order: null,
|
||||
matchData: null,
|
||||
submitting: false,
|
||||
})
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Alert
|
||||
showIcon
|
||||
type="success"
|
||||
message="检测到内部寄售仓库中有可直接匹配履约的在库物品!"
|
||||
description={
|
||||
<div>
|
||||
<div>
|
||||
当前工单原定接单赏金:
|
||||
<strong>
|
||||
¥{Number(consignmentInterceptModal.order?.rewardAmount || 0).toFixed(2)}
|
||||
</strong>
|
||||
</div>
|
||||
<div style={{ color: '#389e0d', marginTop: 4 }}>
|
||||
若使用内部寄售库存履约,<strong>无需向外部打手支付佣金(打手支出 ¥0.00)</strong>
|
||||
,系统将自动核销工单并归档!
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Space size={8}>
|
||||
<Typography.Text strong>匹配结果:</Typography.Text>
|
||||
<Tag color="green">
|
||||
全部匹配: {consignmentInterceptModal.matchData?.exactCount || 0} 件
|
||||
</Tag>
|
||||
<Tag color="gold">
|
||||
部分匹配(高配充低配): {consignmentInterceptModal.matchData?.partialCount || 0} 件
|
||||
</Tag>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
maxHeight: 400,
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
{(consignmentInterceptModal.matchData?.matches || []).map((m) => {
|
||||
const inv = m.inventory
|
||||
const isExact = m.matchType === 'exact'
|
||||
return (
|
||||
<Card
|
||||
key={inv.id}
|
||||
size="small"
|
||||
style={{
|
||||
borderLeft: isExact ? '4px solid #52c41a' : '4px solid #faad14',
|
||||
backgroundColor: isExact ? '#f6ffed' : '#fffbe6',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
|
||||
>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Space size={8} wrap>
|
||||
<strong style={{ fontSize: 15 }}>{inv.itemName}</strong>
|
||||
{isExact ? (
|
||||
<Tag color="green">【全部匹配】</Tag>
|
||||
) : (
|
||||
<Tag color="gold">【部分匹配 / 高配充低配】</Tag>
|
||||
)}
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{inv.inventoryNo}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
|
||||
<div style={{ marginTop: 4 }}>
|
||||
<Space size={4} wrap>
|
||||
<Tag color={inv.system === '苹果' ? 'geekblue' : 'green'}>{inv.system}</Tag>
|
||||
<Tag color="cyan">{inv.serverZone}</Tag>
|
||||
<Tag color="orange">{inv.series}</Tag>
|
||||
<Tag color="gold">{inv.diamondLevel} 钻</Tag>
|
||||
{inv.brillianceCount > 0 ? (
|
||||
<Tag color="magenta">{inv.brillianceCount} 璀璨</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 6, fontSize: 12, color: '#595959' }}>
|
||||
<Space size={16} wrap>
|
||||
<span>
|
||||
内部收购成本:{' '}
|
||||
<strong style={{ color: '#cf1322' }}>
|
||||
¥{(inv.costPriceFen / 100).toFixed(2)}
|
||||
</strong>
|
||||
</span>
|
||||
<span>收购人: {inv.buyerName || '-'}</span>
|
||||
{inv.shopName ? <span>店铺: {inv.shopName}</span> : null}
|
||||
{inv.inboundBatch ? <span>批次: {inv.inboundBatch}</span> : null}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 4, fontSize: 11, color: '#8c8c8c' }}>
|
||||
匹配分析: {m.matchReasons.join(' · ')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginLeft: 16 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={consignmentInterceptModal.submitting}
|
||||
onClick={() => handleFulfillWithConsignment(inv.id)}
|
||||
>
|
||||
使用此库存直接履约
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* 撤单 / 撤销寄售履约弹窗 */}
|
||||
<Modal
|
||||
title={`撤单 / 撤销寄售履约 · ${getDisplayOrderNo(revokeConsignmentModal.order)}`}
|
||||
open={revokeConsignmentModal.open}
|
||||
onCancel={() => {
|
||||
setRevokeConsignmentModal({
|
||||
open: false,
|
||||
order: null,
|
||||
action: 'reopen',
|
||||
reason: '',
|
||||
submitting: false,
|
||||
})
|
||||
}}
|
||||
onOk={handleRevokeConsignmentSubmit}
|
||||
confirmLoading={revokeConsignmentModal.submitting}
|
||||
okText="确认撤单"
|
||||
okType="danger"
|
||||
destroyOnClose
|
||||
>
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message="撤销后,关联寄售物品将退单回库并重新进入待匹配池"
|
||||
description="适用场景:买家退款、客服误选库存、账号密码错误失效或规格不符等情况。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="处理流转方式" required>
|
||||
<Radio.Group
|
||||
value={revokeConsignmentModal.action}
|
||||
onChange={(e) =>
|
||||
setRevokeConsignmentModal((prev) => ({ ...prev, action: e.target.value }))
|
||||
}
|
||||
>
|
||||
<Space direction="vertical">
|
||||
<Radio value="reopen">
|
||||
<div>
|
||||
<strong>退单回库并恢复工单为未指派 (推荐)</strong>
|
||||
<div style={{ fontSize: 12, color: '#8c8c8c' }}>
|
||||
物品重新变为【在库】待匹配,工单恢复为【未指派】,可重新选择其他库存或发布到打手大厅。
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
<Radio value="cancel_order">
|
||||
<div>
|
||||
<strong>退单回库并取消整单</strong>
|
||||
<div style={{ fontSize: 12, color: '#8c8c8c' }}>
|
||||
买家退款时使用。物品退回复库,工单状态变更为【已取消】。
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
<Radio value="archive_inventory">
|
||||
<div>
|
||||
<strong>报废该物品并恢复工单</strong>
|
||||
<div style={{ fontSize: 12, color: '#8c8c8c' }}>
|
||||
如果该寄售物品无法登录/被找回已彻底失效,将该物品标记为【作废】,工单恢复为【未指派】重选。
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="撤单原因说明" required>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
value={revokeConsignmentModal.reason}
|
||||
onChange={(e) =>
|
||||
setRevokeConsignmentModal((prev) => ({ ...prev, reason: e.target.value }))
|
||||
}
|
||||
placeholder="请输入撤单详细原因,如:买家申请退款、选错账号规格、账号密码错误等..."
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user