feat:寄卖增加对接客服,db脚本迁移修复
This commit is contained in:
@@ -24,6 +24,9 @@ const AdminFinancePage = lazy(() => import('@/pages/admin/AdminFinancePage'))
|
||||
const AdminFinanceLedgerPage = lazy(() => import('@/pages/admin/AdminFinanceLedgerPage'))
|
||||
const AdminSalesProfitPage = lazy(() => import('@/pages/admin/AdminSalesProfitPage'))
|
||||
const AdminCostCenterPage = lazy(() => import('@/pages/admin/AdminCostCenterPage'))
|
||||
const AdminConsignmentContactsPage = lazy(
|
||||
() => import('@/pages/admin/AdminConsignmentContactsPage'),
|
||||
)
|
||||
const AdminFundPage = lazy(() => import('@/pages/admin/AdminFundPage'))
|
||||
const AdminAuditLogsPage = lazy(() => import('@/pages/admin/AdminAuditLogsPage'))
|
||||
const AdminKuaishouIndustryPage = lazy(() => import('@/pages/admin/AdminKuaishouIndustryPage'))
|
||||
@@ -166,6 +169,7 @@ function AdminApplication() {
|
||||
</Route>
|
||||
<Route element={<RequireRole roles={['admin']} />}>
|
||||
<Route path="users" element={<AdminUsersPage />} />
|
||||
<Route path="consignment-contacts" element={<AdminConsignmentContactsPage />} />
|
||||
<Route path="platform-shops" element={<AdminPlatformShopsPage />} />
|
||||
<Route path="platform-fulfillment" element={<AdminPlatformFulfillmentPage />} />
|
||||
<Route path="audit-logs" element={<AdminAuditLogsPage />} />
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
resolveManualMatchRequirement,
|
||||
type ManualMatchType,
|
||||
} from '@/utils/consignment-manual-match'
|
||||
import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
import { notifyVoucherConsume } from '@/utils/voucher-consume'
|
||||
|
||||
@@ -45,6 +46,10 @@ type Selected = {
|
||||
/** 供货打手(用于回显供货来源) */
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
/** 对接客服(用于履约前核对归属) */
|
||||
contactSupporterName?: string
|
||||
contactSupporterPhone?: string
|
||||
contactSupporterWechat?: string
|
||||
}
|
||||
|
||||
export type ConsignmentManualMatchModalProps = {
|
||||
@@ -79,6 +84,11 @@ export default function ConsignmentManualMatchModal({
|
||||
workerId: number | null
|
||||
workerName: string
|
||||
} | null>(null)
|
||||
const [inventoryContact, setInventoryContact] = useState<{
|
||||
contactSupporterName: string
|
||||
contactSupporterPhone: string
|
||||
contactSupporterWechat: string
|
||||
} | null>(null)
|
||||
const [selected, setSelected] = useState<Selected | null>(null)
|
||||
const [confirmedPartial, setConfirmedPartial] = useState(false)
|
||||
const [confirmedMismatch, setConfirmedMismatch] = useState(false)
|
||||
@@ -94,6 +104,7 @@ export default function ConsignmentManualMatchModal({
|
||||
setConfirmedMismatch(false)
|
||||
setMatchNote('')
|
||||
setInventoryInboundAt('')
|
||||
setInventoryContact(null)
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
@@ -116,6 +127,11 @@ export default function ConsignmentManualMatchModal({
|
||||
workerId: res.data?.inventory?.workerId ?? null,
|
||||
workerName: res.data?.inventory?.workerName || '',
|
||||
})
|
||||
setInventoryContact({
|
||||
contactSupporterName: res.data?.inventory?.contactSupporterName || '',
|
||||
contactSupporterPhone: res.data?.inventory?.contactSupporterPhone || '',
|
||||
contactSupporterWechat: res.data?.inventory?.contactSupporterWechat || '',
|
||||
})
|
||||
} else if (workOrder) {
|
||||
const res = await fetchWorkOrderConsignmentMatchOptions(workOrder.workOrderId)
|
||||
setOptions(res.data?.options || [])
|
||||
@@ -319,7 +335,8 @@ export default function ConsignmentManualMatchModal({
|
||||
? `${option.inventory.diamondLevel}钻`
|
||||
: ''}{' '}
|
||||
· 货源 {option.inventory.workerName || '未关联打手'} · 结算价 ¥
|
||||
{(Number(option.inventory.costPriceFen || 0) / 100).toFixed(2)}
|
||||
{(Number(option.inventory.costPriceFen || 0) / 100).toFixed(2)} ·
|
||||
对接客服 {formatConsignmentContactSupporter(option.inventory).text}
|
||||
{option.inventory.inboundAt || option.inventory.createdAt ? (
|
||||
<>
|
||||
{' '}
|
||||
@@ -356,6 +373,9 @@ export default function ConsignmentManualMatchModal({
|
||||
conflicts: option.conflicts,
|
||||
workerId: option.inventory.workerId,
|
||||
workerName: option.inventory.workerName,
|
||||
contactSupporterName: option.inventory.contactSupporterName,
|
||||
contactSupporterPhone: option.inventory.contactSupporterPhone,
|
||||
contactSupporterWechat: option.inventory.contactSupporterWechat,
|
||||
})
|
||||
setConfirmedPartial(false)
|
||||
setConfirmedMismatch(false)
|
||||
@@ -439,6 +459,9 @@ export default function ConsignmentManualMatchModal({
|
||||
conflicts: order.conflicts,
|
||||
workerId: inventoryWorker?.workerId ?? null,
|
||||
workerName: inventoryWorker?.workerName || '',
|
||||
contactSupporterName: inventoryContact?.contactSupporterName || '',
|
||||
contactSupporterPhone: inventoryContact?.contactSupporterPhone || '',
|
||||
contactSupporterWechat: inventoryContact?.contactSupporterWechat || '',
|
||||
})
|
||||
setConfirmedPartial(false)
|
||||
setConfirmedMismatch(false)
|
||||
@@ -492,6 +515,9 @@ export default function ConsignmentManualMatchModal({
|
||||
<Tag color="default">内部库存现货(无打手关联)</Tag>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="对接客服">
|
||||
{formatConsignmentContactSupporter(selected).text}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="匹配判定">
|
||||
<Tag color={describeManualMatchType(selected.matchType).color}>
|
||||
{describeManualMatchType(selected.matchType).label}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ApartmentOutlined,
|
||||
BellOutlined,
|
||||
BugOutlined,
|
||||
CustomerServiceOutlined,
|
||||
DashboardOutlined,
|
||||
FileSearchOutlined,
|
||||
LogoutOutlined,
|
||||
@@ -282,6 +283,11 @@ export default function AdminLayout() {
|
||||
label: '配置',
|
||||
children: [
|
||||
{ key: '/admin/users', icon: <TeamOutlined />, label: '后台用户' },
|
||||
{
|
||||
key: '/admin/consignment-contacts',
|
||||
icon: <CustomerServiceOutlined />,
|
||||
label: '对接客服',
|
||||
},
|
||||
{ key: '/admin/platform-shops', icon: <SettingOutlined />, label: '平台配置' },
|
||||
{ key: '/admin/platform-fulfillment', icon: <ShopOutlined />, label: '履约配置' },
|
||||
],
|
||||
@@ -850,6 +856,7 @@ function resolveSelectedKey(pathname: string) {
|
||||
if (pathname.startsWith('/admin/finance')) return '/admin/finance'
|
||||
if (pathname.startsWith('/admin/tasks')) return '/admin/tasks'
|
||||
if (pathname.startsWith('/admin/worker-platform')) return '/admin/worker-platform'
|
||||
if (pathname.startsWith('/admin/consignment-contacts')) return '/admin/consignment-contacts'
|
||||
if (pathname.startsWith('/admin/kuaishou-industry')) return '/admin/kuaishou-industry'
|
||||
if (pathname === '/admin/platform-shops') return '/admin/platform-shops'
|
||||
return pathname
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import { PlusOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import PageHeader from '@/components/admin/PageHeader'
|
||||
import PercentTable from '@/components/common/PercentTable'
|
||||
import {
|
||||
createAdminConsignmentContact,
|
||||
deleteAdminConsignmentContact,
|
||||
fetchAdminConsignmentContacts,
|
||||
setAdminConsignmentContactEnabled,
|
||||
updateAdminConsignmentContact,
|
||||
} from '@/services/admin/consignment'
|
||||
import type { ConsignmentContactSupporter } from '@/types/consignment'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
type ContactForm = {
|
||||
name: string
|
||||
phone?: string
|
||||
wechat?: string
|
||||
note?: string
|
||||
sortOrder?: number
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
const ENABLED_FILTER_OPTIONS = [
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '启用中', value: 'true' },
|
||||
{ label: '已禁用', value: 'false' },
|
||||
]
|
||||
|
||||
/**
|
||||
* 配置 → 对接客服。
|
||||
*
|
||||
* 独立名单(不复用后台账号):打手发布寄卖时必选,后台按人统计提成与快速联系。
|
||||
* 已被寄卖记录引用的客服只能禁用,不能删除,保证历史单归属不丢。
|
||||
*/
|
||||
export default function AdminConsignmentContactsPage() {
|
||||
const { message, modal } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
const [form] = Form.useForm<ContactForm>()
|
||||
|
||||
const [keywordInput, setKeywordInput] = useState('')
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [enabledFilter, setEnabledFilter] = useState('')
|
||||
const [editingContact, setEditingContact] = useState<ConsignmentContactSupporter | null>(null)
|
||||
const [formOpen, setFormOpen] = useState(false)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [actionId, setActionId] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setTimeout(() => setKeyword(keywordInput.trim()), 300)
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [keywordInput])
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['admin-consignment-contacts', keyword, enabledFilter],
|
||||
queryFn: () =>
|
||||
fetchAdminConsignmentContacts({
|
||||
keyword: keyword || undefined,
|
||||
enabled: enabledFilter || undefined,
|
||||
}),
|
||||
})
|
||||
|
||||
const items = query.data?.data.items || []
|
||||
const total = Number(query.data?.data.total || 0)
|
||||
const enabledCount = items.filter((item) => item.enabled).length
|
||||
|
||||
async function reload() {
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin-consignment-contacts'] })
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
setEditingContact(null)
|
||||
form.resetFields()
|
||||
form.setFieldsValue({ enabled: true, sortOrder: 0 })
|
||||
setFormOpen(true)
|
||||
}
|
||||
|
||||
function openEdit(record: ConsignmentContactSupporter) {
|
||||
setEditingContact(record)
|
||||
form.setFieldsValue({
|
||||
name: record.name,
|
||||
phone: record.phone,
|
||||
wechat: record.wechat,
|
||||
note: record.note,
|
||||
sortOrder: record.sortOrder,
|
||||
enabled: record.enabled,
|
||||
})
|
||||
setFormOpen(true)
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
setSubmitting(true)
|
||||
const payload = {
|
||||
name: values.name,
|
||||
phone: values.phone || '',
|
||||
wechat: values.wechat || '',
|
||||
note: values.note || '',
|
||||
sortOrder: Number(values.sortOrder || 0),
|
||||
enabled: values.enabled !== false,
|
||||
}
|
||||
if (editingContact) {
|
||||
await updateAdminConsignmentContact(editingContact.id, payload)
|
||||
message.success('对接客服已更新')
|
||||
} else {
|
||||
await createAdminConsignmentContact(payload)
|
||||
message.success('对接客服已新增')
|
||||
}
|
||||
setFormOpen(false)
|
||||
setEditingContact(null)
|
||||
await reload()
|
||||
} catch (error: any) {
|
||||
if (error?.errorFields) return
|
||||
message.error(error?.message || '保存对接客服失败')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleEnabled(record: ConsignmentContactSupporter, enabled: boolean) {
|
||||
setActionId(record.id)
|
||||
try {
|
||||
await setAdminConsignmentContactEnabled(record.id, enabled)
|
||||
message.success(enabled ? '已启用该对接客服' : '已禁用该对接客服')
|
||||
await reload()
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '更新对接客服状态失败')
|
||||
} finally {
|
||||
setActionId(null)
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(record: ConsignmentContactSupporter) {
|
||||
modal.confirm({
|
||||
title: `删除对接客服「${record.name}」?`,
|
||||
content:
|
||||
'只有未被任何寄卖记录使用的客服可以删除。已被使用的请改为「禁用」,历史寄卖单仍会保留对接客服信息。',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteAdminConsignmentContact(record.id)
|
||||
message.success('对接客服已删除')
|
||||
await reload()
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '删除对接客服失败')
|
||||
throw error
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const columns: TableColumnsType<ConsignmentContactSupporter> = [
|
||||
{
|
||||
title: '姓名',
|
||||
key: 'name',
|
||||
width: 160,
|
||||
render: (_, record) => (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text strong>{record.name}</Text>
|
||||
{record.note ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{record.note}
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
width: 150,
|
||||
render: (value: string) => value || <Text type="secondary">-</Text>,
|
||||
},
|
||||
{
|
||||
title: '微信号',
|
||||
dataIndex: 'wechat',
|
||||
width: 160,
|
||||
render: (value: string) => value || <Text type="secondary">-</Text>,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortOrder',
|
||||
width: 80,
|
||||
render: (value: number) => value || 0,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'enabled',
|
||||
width: 110,
|
||||
render: (_, record) => (
|
||||
<Space size={6}>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={record.enabled}
|
||||
loading={actionId === record.id}
|
||||
checkedChildren="启用"
|
||||
unCheckedChildren="禁用"
|
||||
onChange={(checked) => void handleToggleEnabled(record, checked)}
|
||||
/>
|
||||
<Tag color={record.enabled ? 'green' : 'default'} style={{ marginInlineEnd: 0 }}>
|
||||
{record.enabled ? '启用中' : '已禁用'}
|
||||
</Tag>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updatedAt',
|
||||
width: 170,
|
||||
render: (value: string) => formatDateTime(value),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 140,
|
||||
render: (_, record) => (
|
||||
<Space size={4}>
|
||||
<Button size="small" type="link" onClick={() => openEdit(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button size="small" type="link" danger onClick={() => handleDelete(record)}>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="对接客服"
|
||||
description="打手发布寄卖时必须选择对接客服;本名单独立于后台账号,用于提成归属与联系。"
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||||
新增对接客服
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card size="small" style={{ marginBottom: 16 }}>
|
||||
<Space wrap size={12}>
|
||||
<Input
|
||||
allowClear
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder="搜索姓名 / 手机 / 微信 / 备注"
|
||||
value={keywordInput}
|
||||
onChange={(event) => setKeywordInput(event.target.value)}
|
||||
style={{ width: 280 }}
|
||||
/>
|
||||
<Select
|
||||
value={enabledFilter}
|
||||
options={ENABLED_FILTER_OPTIONS}
|
||||
onChange={setEnabledFilter}
|
||||
style={{ width: 140 }}
|
||||
/>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
|
||||
刷新
|
||||
</Button>
|
||||
<Text type="secondary">
|
||||
共 {total} 个,本页启用 {enabledCount} 个
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<PercentTable<ConsignmentContactSupporter>
|
||||
rowKey="id"
|
||||
loading={query.isLoading}
|
||||
columns={columns}
|
||||
dataSource={items}
|
||||
pagination={false}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={editingContact ? `编辑对接客服 · ${editingContact.name}` : '新增对接客服'}
|
||||
open={formOpen}
|
||||
onCancel={() => {
|
||||
setFormOpen(false)
|
||||
setEditingContact(null)
|
||||
}}
|
||||
onOk={() => void handleSubmit()}
|
||||
confirmLoading={submitting}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="姓名"
|
||||
rules={[{ required: true, message: '请填写对接客服姓名' }]}
|
||||
>
|
||||
<Input placeholder="如:小张" maxLength={60} />
|
||||
</Form.Item>
|
||||
<Space size={12} style={{ width: '100%' }} align="start">
|
||||
<Form.Item name="phone" label="手机号" style={{ width: 240 }}>
|
||||
<Input placeholder="选填" maxLength={30} />
|
||||
</Form.Item>
|
||||
<Form.Item name="wechat" label="微信号" style={{ width: 240 }}>
|
||||
<Input placeholder="选填" maxLength={60} />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Form.Item name="note" label="备注">
|
||||
<Input.TextArea rows={2} placeholder="如:负责苏尔南寄卖 / 白班" maxLength={200} />
|
||||
</Form.Item>
|
||||
<Space size={24} align="start">
|
||||
<Form.Item
|
||||
name="sortOrder"
|
||||
label="排序"
|
||||
tooltip="数字越小越靠前;打手端默认选中第一个启用项"
|
||||
>
|
||||
<InputNumber min={0} max={9999} style={{ width: 140 }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="enabled" label="启用" valuePropName="checked">
|
||||
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { useState } from 'react'
|
||||
import ConsignmentEvidenceGallery from '@/components/admin/ConsignmentEvidenceGallery'
|
||||
import {
|
||||
archiveAdminConsignmentInventory,
|
||||
fetchAdminConsignmentContacts,
|
||||
fetchAdminConsignmentInventories,
|
||||
fetchAdminConsignmentStats,
|
||||
returnAdminConsignmentInventory,
|
||||
@@ -40,6 +41,7 @@ import ConsignmentManualMatchModal from '@/components/admin/ConsignmentManualMat
|
||||
import type { ConsignmentInventory, GiftEvidenceReviewStatus } from '@/types/consignment'
|
||||
import { fetchAdminSession } from '@/services/admin/auth'
|
||||
import { resolveAdminPermission } from '@/utils/admin-auth'
|
||||
import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
|
||||
import { describeGiftEvidence, resolveGiftEvidenceBadge } from '@/utils/consignment-evidence'
|
||||
import { CONSIGNMENT_SYSTEM_OPTIONS, CONSIGNMENT_ZONE_OPTIONS } from '@/utils/consignment-spec'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
@@ -174,6 +176,18 @@ export default function ConsignmentInventoryPanel() {
|
||||
name: w.displayName || w.username,
|
||||
}))
|
||||
|
||||
// 对接客服名单:编辑现货时可改派对接人(含已禁用项,历史数据需要能显示原值)
|
||||
const contactsQuery = useQuery({
|
||||
queryKey: ['admin-consignment-contacts'],
|
||||
queryFn: () => fetchAdminConsignmentContacts(),
|
||||
})
|
||||
const contactSupporterOptions = (contactsQuery.data?.data.items || []).map((item) => ({
|
||||
label: `${item.name}${item.enabled ? '' : '(已禁用)'}${
|
||||
item.phone ? ` · ${item.phone}` : item.wechat ? ` · ${item.wechat}` : ''
|
||||
}`,
|
||||
value: item.id,
|
||||
}))
|
||||
|
||||
// 数据查询
|
||||
const inventoriesQuery = useQuery({
|
||||
queryKey: [
|
||||
@@ -255,6 +269,7 @@ export default function ConsignmentInventoryPanel() {
|
||||
workerId: values.workerId || undefined,
|
||||
workerName: selectedWorker?.name || editingItem.workerName || undefined,
|
||||
workerPhone: selectedWorker?.phone || editingItem.workerPhone || undefined,
|
||||
contactSupporterId: values.contactSupporterId ?? null,
|
||||
remark: values.remark,
|
||||
allowMatching: values.allowMatching !== false,
|
||||
})
|
||||
@@ -456,6 +471,22 @@ export default function ConsignmentInventoryPanel() {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '对接客服',
|
||||
key: 'contactSupporter',
|
||||
width: 150,
|
||||
render: (_, record) => {
|
||||
const contact = formatConsignmentContactSupporter(record)
|
||||
return (
|
||||
<div style={{ fontSize: 12 }}>
|
||||
<div style={{ fontWeight: 600 }}>{contact.name || contact.text}</div>
|
||||
{contact.contact ? (
|
||||
<div style={{ color: '#8c8c8c', marginTop: 2 }}>{contact.contact}</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '凭证',
|
||||
key: 'evidence',
|
||||
@@ -990,6 +1021,24 @@ export default function ConsignmentInventoryPanel() {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={14}>
|
||||
<Form.Item
|
||||
name="contactSupporterId"
|
||||
label="对接客服"
|
||||
tooltip="用于提成归属与联系;发布寄卖时由卖家必选"
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="选择对接客服(选填)"
|
||||
options={contactSupporterOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="sellerPaymentInfo" label="卖家收款信息">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -1082,6 +1131,23 @@ export default function ConsignmentInventoryPanel() {
|
||||
<Text type="secondary">内部自营现货</Text>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="对接客服">
|
||||
{(() => {
|
||||
const contact = formatConsignmentContactSupporter(viewingItem)
|
||||
return contact.name ? (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text strong>{contact.name}</Text>
|
||||
{contact.contact ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{contact.contact}
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
) : (
|
||||
<Text type="secondary">未指定</Text>
|
||||
)
|
||||
})()}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="履约押金要求">
|
||||
{viewingItem.depositAmountFen ? (
|
||||
<Tag color="gold">¥{(viewingItem.depositAmountFen / 100).toFixed(0)}</Tag>
|
||||
|
||||
@@ -40,6 +40,7 @@ import { fetchAdminSession } from '@/services/admin/auth'
|
||||
import { fetchAdminWorkerUsers } from '@/services/admin/worker-platform'
|
||||
import type { ConsignmentSubmission, GiftEvidenceReviewStatus } from '@/types/consignment'
|
||||
import { resolveAdminPermission } from '@/utils/admin-auth'
|
||||
import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
|
||||
import { describeGiftEvidence, resolveGiftEvidenceBadge } from '@/utils/consignment-evidence'
|
||||
import { CONSIGNMENT_SYSTEM_OPTIONS, CONSIGNMENT_ZONE_OPTIONS } from '@/utils/consignment-spec'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
@@ -271,6 +272,22 @@ export default function ConsignmentSubmissionsPanel() {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '对接客服',
|
||||
key: 'contactSupporter',
|
||||
width: 150,
|
||||
render: (_, record) => {
|
||||
const contact = formatConsignmentContactSupporter(record)
|
||||
return (
|
||||
<div style={{ fontSize: 12 }}>
|
||||
<div style={{ fontWeight: 600 }}>{contact.name || contact.text}</div>
|
||||
{contact.contact ? (
|
||||
<div style={{ color: '#8c8c8c', marginTop: 2 }}>{contact.contact}</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '提报凭证(游戏主页截图)',
|
||||
key: 'submissionScreenshots',
|
||||
@@ -536,6 +553,10 @@ export default function ConsignmentSubmissionsPanel() {
|
||||
<span>
|
||||
联系方式:<Text strong>{approveModalItem?.contactInfo || '-'}</Text>
|
||||
</span>
|
||||
<span>
|
||||
对接客服:
|
||||
<Text strong>{formatConsignmentContactSupporter(approveModalItem).text}</Text>
|
||||
</span>
|
||||
<span>
|
||||
游戏ID:<Text strong>{approveModalItem?.sellerGameId || '-'}</Text>
|
||||
</span>
|
||||
@@ -852,6 +873,23 @@ export default function ConsignmentSubmissionsPanel() {
|
||||
<Text type="secondary">未关联打手账号</Text>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="对接客服">
|
||||
{(() => {
|
||||
const contact = formatConsignmentContactSupporter(viewingSubmission)
|
||||
return contact.name ? (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text strong>{contact.name}</Text>
|
||||
{contact.contact ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{contact.contact}
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
) : (
|
||||
<Text type="secondary">未指定</Text>
|
||||
)
|
||||
})()}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="履约押金要求">
|
||||
{viewingSubmission.depositAmountFen ? (
|
||||
<Tag color="gold">¥{(viewingSubmission.depositAmountFen / 100).toFixed(0)}</Tag>
|
||||
|
||||
@@ -92,6 +92,7 @@ import { notifyVoucherConsume } from '@/utils/voucher-consume'
|
||||
import type { ConsignmentFulfillmentInfo, WorkOrderInventoryMatchCheck } from '@/types/consignment'
|
||||
import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
|
||||
import { canManuallyMatchConsignmentOrder } from '@/utils/consignment-manual-match'
|
||||
import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
|
||||
import {
|
||||
isConsignmentOrder,
|
||||
isInternalConsignmentOrder,
|
||||
@@ -1644,10 +1645,15 @@ export default function WorkOrdersPanel() {
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{consignment ? (
|
||||
<Tag color="purple" style={{ marginTop: 2, width: 'fit-content' }}>
|
||||
寄卖单号: {resolveConsignmentNo(consignment) || '-'} (成本: ¥
|
||||
{((Number(consignment.costPriceFen) || 0) / 100).toFixed(2)})
|
||||
</Tag>
|
||||
<Space size={[4, 4]} wrap>
|
||||
<Tag color="purple" style={{ marginTop: 2, width: 'fit-content' }}>
|
||||
寄卖单号: {resolveConsignmentNo(consignment) || '-'} (成本: ¥
|
||||
{((Number(consignment.costPriceFen) || 0) / 100).toFixed(2)})
|
||||
</Tag>
|
||||
<Tag color="cyan" style={{ marginTop: 2, width: 'fit-content' }}>
|
||||
对接客服: {formatConsignmentContactSupporter(consignment).text}
|
||||
</Tag>
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
@@ -2317,6 +2323,11 @@ export default function WorkOrdersPanel() {
|
||||
</Typography.Text>
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
{detailConsignment ? (
|
||||
<Descriptions.Item label="对接客服">
|
||||
{formatConsignmentContactSupporter(detailConsignment).text}
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
<Descriptions.Item label="状态">
|
||||
<Tag color={resolveStatusColor(detailOrder.status)}>
|
||||
{formatStatus(detailOrder.status)}
|
||||
@@ -2655,6 +2666,13 @@ export default function WorkOrdersPanel() {
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
{isConsignment ? (
|
||||
<Descriptions.Item label="对接客服" span={2}>
|
||||
<span className="worker-order-detail-value">
|
||||
{formatConsignmentContactSupporter(consignmentInfo).text}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
<Descriptions.Item
|
||||
label={isConsignment ? '卖家备注' : '打手备注'}
|
||||
span={2}
|
||||
@@ -4095,6 +4113,9 @@ export default function WorkOrdersPanel() {
|
||||
供货打手: {inv.workerName} ({inv.workerPhone || '无手机号'})
|
||||
</Tag>
|
||||
) : null}
|
||||
<Tag color="cyan">
|
||||
对接客服: {formatConsignmentContactSupporter(inv).text}
|
||||
</Tag>
|
||||
{inv.depositAmountFen != null ? (
|
||||
<Tag color="warning">
|
||||
履约押金: ¥{(inv.depositAmountFen / 100).toFixed(2)}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
Pagination,
|
||||
Radio,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Tabs,
|
||||
Tag,
|
||||
@@ -43,6 +44,7 @@ import ImageUpload from '@/components/files/ImageUpload'
|
||||
import { WorkerContactModal } from '@/components/worker/WorkerContactModal'
|
||||
import {
|
||||
confirmWorkerConsignmentFriendAdded,
|
||||
fetchWorkerConsignmentContacts,
|
||||
fetchWorkerConsignmentPriceSuggestion,
|
||||
fetchWorkerConsignments,
|
||||
fetchWorkerProfile,
|
||||
@@ -56,6 +58,7 @@ import {
|
||||
SURNAN_PUBLISH_SYSTEM_OPTIONS,
|
||||
SURNAN_PUBLISH_ZONE_OPTIONS,
|
||||
} from '@/utils/consignment-spec'
|
||||
import { formatConsignmentContactSupporter } from '@/utils/consignment-contact'
|
||||
import { formatGiftCooldownRemaining } from '@/utils/consignment-cooldown'
|
||||
import { canWorkerPublishConsignment } from '@/utils/consignment-whitelist'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
@@ -199,6 +202,15 @@ export default function WorkerConsignmentPage() {
|
||||
}),
|
||||
})
|
||||
|
||||
// 发布寄卖必填「对接客服」:只取启用名单,默认选第一个
|
||||
const consignmentContactsQuery = useQuery({
|
||||
queryKey: ['worker-consignment-contacts'],
|
||||
queryFn: () => fetchWorkerConsignmentContacts(),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
const contactSupporters = consignmentContactsQuery.data?.data.items || []
|
||||
const firstContactSupporterId = contactSupporters[0]?.id ?? null
|
||||
|
||||
// 切换标签页时自动刷新最新数据
|
||||
useEffect(() => {
|
||||
if (activeTab === 'records') {
|
||||
@@ -261,6 +273,14 @@ export default function WorkerConsignmentPage() {
|
||||
}
|
||||
}, [worker, form])
|
||||
|
||||
// 名单异步加载完成后补默认值;用户已经选过则不覆盖
|
||||
useEffect(() => {
|
||||
if (firstContactSupporterId == null) return
|
||||
if (form.getFieldValue('contactSupporterId') == null) {
|
||||
form.setFieldsValue({ contactSupporterId: firstContactSupporterId })
|
||||
}
|
||||
}, [firstContactSupporterId, form])
|
||||
|
||||
useEffect(() => {
|
||||
if (profileQuery.isLoading) return
|
||||
if (typeof window === 'undefined') return
|
||||
@@ -368,6 +388,15 @@ export default function WorkerConsignmentPage() {
|
||||
return
|
||||
}
|
||||
|
||||
if (contactSupporters.length === 0) {
|
||||
message.error('暂无可选的对接客服,请联系管理员在「配置 - 对接客服」中添加后再发布')
|
||||
return
|
||||
}
|
||||
if (values.contactSupporterId == null) {
|
||||
message.error('请选择对接客服')
|
||||
return
|
||||
}
|
||||
|
||||
const urls = screenshots.map((f) => f.url).filter(Boolean)
|
||||
if (urls.length === 0) {
|
||||
message.error('请上传游戏主页截图以供核验')
|
||||
@@ -412,10 +441,14 @@ export default function WorkerConsignmentPage() {
|
||||
screenshotsJson: urls,
|
||||
sellerNote: values.sellerNote,
|
||||
depositAmountFen: publishDepositFen,
|
||||
contactSupporterId: Number(values.contactSupporterId),
|
||||
})
|
||||
|
||||
message.success(`寄卖提报成功,已暂扣履约押金 ¥${publishDepositYuan.toFixed(0)}`)
|
||||
form.resetFields()
|
||||
if (firstContactSupporterId != null) {
|
||||
form.setFieldsValue({ contactSupporterId: firstContactSupporterId })
|
||||
}
|
||||
setPriceState({ status: 'idle' })
|
||||
lastPriceQueryKeyRef.current = ''
|
||||
setScreenshots([])
|
||||
@@ -662,6 +695,14 @@ export default function WorkerConsignmentPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
{!consignmentContactsQuery.isLoading && contactSupporters.length === 0 ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="暂无可选的对接客服,请联系管理员在「配置 - 对接客服」中添加后再发布"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Divider plain className="worker-consignment-divider">
|
||||
规格与价格
|
||||
</Divider>
|
||||
@@ -807,6 +848,35 @@ export default function WorkerConsignmentPage() {
|
||||
</Divider>
|
||||
|
||||
<Row gutter={[12, 0]}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Form.Item
|
||||
name="contactSupporterId"
|
||||
label="对接客服"
|
||||
rules={[{ required: true, message: '请选择对接客服' }]}
|
||||
extra={
|
||||
contactSupporters.length === 0
|
||||
? '暂无可选客服,请联系管理员配置'
|
||||
: '用于后续提成归属与联系'
|
||||
}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择对接客服"
|
||||
options={contactSupporters.map((item) => ({
|
||||
value: item.id,
|
||||
label: `${item.name}${
|
||||
item.phone
|
||||
? ` · ${item.phone}`
|
||||
: item.wechat
|
||||
? ` · ${item.wechat}`
|
||||
: ''
|
||||
}`,
|
||||
}))}
|
||||
notFoundContent="暂无启用中的对接客服"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Form.Item
|
||||
name="sellerContact"
|
||||
@@ -1334,6 +1404,9 @@ function ConsignmentRecordCard({
|
||||
<span>
|
||||
押金 <strong>¥{(Number(record.depositAmountFen || 0) / 100).toFixed(2)}</strong>
|
||||
</span>
|
||||
<span>
|
||||
对接客服 <strong>{formatConsignmentContactSupporter(record).text}</strong>
|
||||
</span>
|
||||
{record.workOrderId ? <span>履约单 #{record.workOrderId}</span> : null}
|
||||
</div>
|
||||
{record.reviewNote ? (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiGet, apiPatch, apiPost, apiPut } from '@/lib/http'
|
||||
import { apiDelete, apiGet, apiPatch, apiPost, apiPut } from '@/lib/http'
|
||||
import type {
|
||||
ConsignmentContactSupporter,
|
||||
ConsignmentInventory,
|
||||
ConsignmentStatsSummary,
|
||||
ConsignmentSubmission,
|
||||
@@ -8,6 +9,43 @@ import type {
|
||||
WorkOrderInventoryMatchOptions,
|
||||
} from '@/types/consignment'
|
||||
|
||||
// --- 对接客服名单 ---
|
||||
|
||||
export function fetchAdminConsignmentContacts(params?: { keyword?: string; enabled?: string }) {
|
||||
return apiGet<{ items: ConsignmentContactSupporter[]; total: number }>(
|
||||
'/api/v1/admin/worker-platform/consignment/contacts',
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
export function createAdminConsignmentContact(payload: Record<string, unknown>) {
|
||||
return apiPost<ConsignmentContactSupporter>(
|
||||
'/api/v1/admin/worker-platform/consignment/contacts',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function updateAdminConsignmentContact(
|
||||
id: number | string,
|
||||
payload: Record<string, unknown>,
|
||||
) {
|
||||
return apiPut<ConsignmentContactSupporter>(
|
||||
`/api/v1/admin/worker-platform/consignment/contacts/${id}`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function setAdminConsignmentContactEnabled(id: number | string, enabled: boolean) {
|
||||
return apiPatch<ConsignmentContactSupporter>(
|
||||
`/api/v1/admin/worker-platform/consignment/contacts/${id}/enabled`,
|
||||
{ enabled },
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteAdminConsignmentContact(id: number | string) {
|
||||
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/worker-platform/consignment/contacts/${id}`)
|
||||
}
|
||||
|
||||
// --- 库存管理 ---
|
||||
|
||||
export function fetchAdminConsignmentInventories(params?: Record<string, unknown>) {
|
||||
|
||||
@@ -25,7 +25,11 @@ import type {
|
||||
WorkerUser,
|
||||
WorkerWalletLedger,
|
||||
} from '@/types/worker-platform'
|
||||
import type { ConsignmentBuyerInfo, ConsignmentSellerInfo } from '@/types/consignment'
|
||||
import type {
|
||||
ConsignmentBuyerInfo,
|
||||
ConsignmentContactSupporter,
|
||||
ConsignmentSellerInfo,
|
||||
} from '@/types/consignment'
|
||||
|
||||
import { getWorkerDeviceId } from '@/utils/worker-auth'
|
||||
|
||||
@@ -392,6 +396,8 @@ export type WorkerConsignmentPublishPayload = {
|
||||
screenshotsJson: string[]
|
||||
sellerNote?: string
|
||||
depositAmountFen?: number
|
||||
/** 对接客服名单 id(必填,发布时选定) */
|
||||
contactSupporterId: number
|
||||
}
|
||||
|
||||
export type WorkerConsignmentItem = {
|
||||
@@ -409,6 +415,10 @@ export type WorkerConsignmentItem = {
|
||||
resultInventoryId?: number | string | null
|
||||
screenshotsJson: string[]
|
||||
contentJson: Record<string, unknown>
|
||||
contactSupporterId?: number | null
|
||||
contactSupporterName?: string
|
||||
contactSupporterPhone?: string
|
||||
contactSupporterWechat?: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
@@ -450,6 +460,10 @@ export type WorkerConsignmentRecord = {
|
||||
brillianceCount: number
|
||||
askingPriceFen: number
|
||||
depositAmountFen: number
|
||||
contactSupporterId?: number | null
|
||||
contactSupporterName?: string
|
||||
contactSupporterPhone?: string
|
||||
contactSupporterWechat?: string
|
||||
reviewNote: string
|
||||
screenshotsJson: string[]
|
||||
giftEvidenceImages?: string[]
|
||||
@@ -507,6 +521,11 @@ export function publishWorkerConsignment(payload: WorkerConsignmentPublishPayloa
|
||||
return apiPost<WorkerConsignmentItem>('/api/v1/worker/consignment/publish', payload)
|
||||
}
|
||||
|
||||
/** 发布寄卖时的「对接客服」下拉(仅启用中的名单)。 */
|
||||
export function fetchWorkerConsignmentContacts() {
|
||||
return apiGet<{ items: ConsignmentContactSupporter[] }>('/api/v1/worker/consignment/contacts')
|
||||
}
|
||||
|
||||
export function fetchWorkerConsignments(params: WorkerConsignmentListParams = {}) {
|
||||
return apiGet<WorkerConsignmentListResponse>(
|
||||
'/api/v1/worker/consignment/my-items',
|
||||
|
||||
@@ -7,6 +7,31 @@
|
||||
*/
|
||||
export type GiftEvidenceReviewStatus = 'none' | 'pending' | 'accepted' | 'submitted'
|
||||
|
||||
/**
|
||||
* 寄卖「对接客服」名单项。
|
||||
*
|
||||
* 独立名单(不是后台账号):发布寄卖时必选,后台用于统计提成与联系。
|
||||
*/
|
||||
export type ConsignmentContactSupporter = {
|
||||
id: number
|
||||
name: string
|
||||
phone: string
|
||||
wechat: string
|
||||
note: string
|
||||
enabled: boolean
|
||||
sortOrder: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
/** 提报单 / 现货 / 工单物料上的对接客服快照字段。 */
|
||||
export type ConsignmentContactFields = {
|
||||
contactSupporterId?: number | null
|
||||
contactSupporterName?: string
|
||||
contactSupporterPhone?: string
|
||||
contactSupporterWechat?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 客服列表 / 详情接口额外返回的寄卖凭证字段(提报列表与库存列表都有)。
|
||||
*
|
||||
@@ -34,51 +59,52 @@ export type ConsignmentEvidenceFields = {
|
||||
giftEvidenceReviewStatus?: GiftEvidenceReviewStatus
|
||||
}
|
||||
|
||||
export type ConsignmentInventory = ConsignmentEvidenceFields & {
|
||||
id: number
|
||||
inventoryNo: string
|
||||
gameName: string
|
||||
categoryId: number | null
|
||||
categoryName?: string
|
||||
itemName: string
|
||||
system: string
|
||||
serverZone: string
|
||||
series: string
|
||||
diamondLevel: number
|
||||
brillianceCount: number
|
||||
costPriceFen: number
|
||||
buyerName: string
|
||||
shopName: string
|
||||
inboundBatch: string
|
||||
sellerContact: string
|
||||
sellerGameId: string
|
||||
sellerPaymentInfo: string
|
||||
sellerPhone: string
|
||||
credentialsJson: Record<string, unknown>
|
||||
screenshotsJson: string[]
|
||||
remark: string
|
||||
allowMatching: boolean
|
||||
status: 'in_stock' | 'consumed' | 'archived'
|
||||
allocationStatus: string
|
||||
boundWorkOrderId: number | null
|
||||
boundPlatformOrderId: string
|
||||
sourceType: 'manual' | 'worker_portal' | 'recycle'
|
||||
sourceSubmissionId: number | null
|
||||
/** 来源提报单号(提报单号即寄卖单号);客服手工入库的现货为空字符串。 */
|
||||
sourceSubmissionNo?: string
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
/** 被指派执行本次履约的打手(卖家之外的第三人);workerId 始终是卖家。 */
|
||||
assignedWorkerId?: number | null
|
||||
depositAmountFen?: number
|
||||
inboundAt: string
|
||||
consumedAt: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
/** 关联履约工单的买家资料(加好友及主页截图) */
|
||||
buyerInfo?: ConsignmentBuyerInfo | null
|
||||
}
|
||||
export type ConsignmentInventory = ConsignmentEvidenceFields &
|
||||
ConsignmentContactFields & {
|
||||
id: number
|
||||
inventoryNo: string
|
||||
gameName: string
|
||||
categoryId: number | null
|
||||
categoryName?: string
|
||||
itemName: string
|
||||
system: string
|
||||
serverZone: string
|
||||
series: string
|
||||
diamondLevel: number
|
||||
brillianceCount: number
|
||||
costPriceFen: number
|
||||
buyerName: string
|
||||
shopName: string
|
||||
inboundBatch: string
|
||||
sellerContact: string
|
||||
sellerGameId: string
|
||||
sellerPaymentInfo: string
|
||||
sellerPhone: string
|
||||
credentialsJson: Record<string, unknown>
|
||||
screenshotsJson: string[]
|
||||
remark: string
|
||||
allowMatching: boolean
|
||||
status: 'in_stock' | 'consumed' | 'archived'
|
||||
allocationStatus: string
|
||||
boundWorkOrderId: number | null
|
||||
boundPlatformOrderId: string
|
||||
sourceType: 'manual' | 'worker_portal' | 'recycle'
|
||||
sourceSubmissionId: number | null
|
||||
/** 来源提报单号(提报单号即寄卖单号);客服手工入库的现货为空字符串。 */
|
||||
sourceSubmissionNo?: string
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
/** 被指派执行本次履约的打手(卖家之外的第三人);workerId 始终是卖家。 */
|
||||
assignedWorkerId?: number | null
|
||||
depositAmountFen?: number
|
||||
inboundAt: string
|
||||
consumedAt: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
/** 关联履约工单的买家资料(加好友及主页截图) */
|
||||
buyerInfo?: ConsignmentBuyerInfo | null
|
||||
}
|
||||
|
||||
export type ConsignmentSellerInfo = {
|
||||
characterName: string
|
||||
@@ -112,40 +138,46 @@ export type ConsignmentFulfillmentInfo = {
|
||||
costPriceFen?: number
|
||||
fulfillmentType?: string
|
||||
workerId?: number | null
|
||||
/** 对接客服(履约时从现货复制到工单物料,用于工单侧联系) */
|
||||
contactSupporterId?: number | null
|
||||
contactSupporterName?: string
|
||||
contactSupporterPhone?: string
|
||||
contactSupporterWechat?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ConsignmentSubmission = ConsignmentEvidenceFields & {
|
||||
id: number
|
||||
submissionNo: string
|
||||
gameName: string
|
||||
categoryId: number | null
|
||||
categoryName?: string
|
||||
itemName: string
|
||||
system: string
|
||||
serverZone: string
|
||||
series: string
|
||||
diamondLevel: number
|
||||
brillianceCount: number
|
||||
sellerGameId: string
|
||||
sellerPaymentInfo: string
|
||||
sellerPhone: string
|
||||
contactInfo: string
|
||||
contentJson: Record<string, unknown>
|
||||
screenshotsJson: string[]
|
||||
askingPriceFen: number
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
status: 'pending_review' | 'approved' | 'rejected'
|
||||
reviewNote: string
|
||||
reviewerName: string
|
||||
reviewedAt: string | null
|
||||
resultInventoryId: number | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
export type ConsignmentSubmission = ConsignmentEvidenceFields &
|
||||
ConsignmentContactFields & {
|
||||
id: number
|
||||
submissionNo: string
|
||||
gameName: string
|
||||
categoryId: number | null
|
||||
categoryName?: string
|
||||
itemName: string
|
||||
system: string
|
||||
serverZone: string
|
||||
series: string
|
||||
diamondLevel: number
|
||||
brillianceCount: number
|
||||
sellerGameId: string
|
||||
sellerPaymentInfo: string
|
||||
sellerPhone: string
|
||||
contactInfo: string
|
||||
contentJson: Record<string, unknown>
|
||||
screenshotsJson: string[]
|
||||
askingPriceFen: number
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
status: 'pending_review' | 'approved' | 'rejected'
|
||||
reviewNote: string
|
||||
reviewerName: string
|
||||
reviewedAt: string | null
|
||||
resultInventoryId: number | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type ConsignmentStatsSummary = {
|
||||
totalInStockCount: number
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/** 寄卖「对接客服」展示工具:后台列表、详情、工单与打手端统一口径。 */
|
||||
export type ConsignmentContactDisplay = {
|
||||
/** 姓名;没有对接客服时为空串 */
|
||||
name: string
|
||||
/** 联系方式(电话 / 微信),没有时为空串 */
|
||||
contact: string
|
||||
/** 姓名 + 联系方式,列表里直接展示 */
|
||||
text: string
|
||||
}
|
||||
|
||||
type ConsignmentContactLike =
|
||||
| {
|
||||
contactSupporterName?: string | null
|
||||
contactSupporterPhone?: string | null
|
||||
contactSupporterWechat?: string | null
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
|
||||
export function formatConsignmentContactSupporter(
|
||||
source: ConsignmentContactLike,
|
||||
emptyText = '未指定',
|
||||
): ConsignmentContactDisplay {
|
||||
const name = String(source?.contactSupporterName || '').trim()
|
||||
const phone = String(source?.contactSupporterPhone || '').trim()
|
||||
const wechat = String(source?.contactSupporterWechat || '').trim()
|
||||
|
||||
const parts: string[] = []
|
||||
if (phone) parts.push(`电话 ${phone}`)
|
||||
if (wechat) parts.push(`微信 ${wechat}`)
|
||||
const contact = parts.join(' · ')
|
||||
|
||||
if (!name) {
|
||||
return { name: '', contact, text: contact || emptyText }
|
||||
}
|
||||
return { name, contact, text: contact ? `${name} · ${contact}` : name }
|
||||
}
|
||||
Reference in New Issue
Block a user