新增快手电子凭证后台工具
This commit is contained in:
@@ -10,6 +10,7 @@ const AdminAuditLogsPage = lazy(() => import('@/pages/admin/AdminAuditLogsPage')
|
||||
const AdminCloudtentaclesRecordsPage = lazy(
|
||||
() => import('@/pages/admin/AdminCloudtentaclesRecordsPage'),
|
||||
)
|
||||
const AdminKuaishouIndustryPage = lazy(() => import('@/pages/admin/AdminKuaishouIndustryPage'))
|
||||
const AdminLoginPage = lazy(() => import('@/pages/admin/AdminLoginPage'))
|
||||
const AdminOrderDetailPage = lazy(() => import('@/pages/admin/AdminOrderDetailPage'))
|
||||
const AdminOrdersPage = lazy(() => import('@/pages/admin/AdminOrdersPage'))
|
||||
@@ -64,6 +65,7 @@ export default function App() {
|
||||
<Route path="orders/:orderId" element={<AdminOrderDetailPage />} />
|
||||
<Route path="tasks" element={<AdminTasksPage />} />
|
||||
<Route path="tasks/:taskId" element={<AdminTaskDetailPage />} />
|
||||
<Route path="kuaishou-industry" element={<AdminKuaishouIndustryPage />} />
|
||||
<Route path="cloudtentacles-records" element={<AdminCloudtentaclesRecordsPage />} />
|
||||
<Route element={<RequireRole roles={['admin']} />}>
|
||||
<Route path="users" element={<AdminUsersPage />} />
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
OrderedListOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
SettingOutlined,
|
||||
ShopOutlined,
|
||||
TeamOutlined,
|
||||
@@ -44,6 +45,7 @@ export default function AdminLayout() {
|
||||
{ key: '/admin/dashboard', icon: <DashboardOutlined />, label: '概览' },
|
||||
{ key: '/admin/orders', icon: <OrderedListOutlined />, label: '订单' },
|
||||
{ key: '/admin/tasks', icon: <UnorderedListOutlined />, label: '任务' },
|
||||
{ key: '/admin/kuaishou-industry', icon: <SafetyCertificateOutlined />, label: '电子凭证' },
|
||||
{ key: '/admin/cloudtentacles-records', icon: <FileSearchOutlined />, label: '发货记录' },
|
||||
]
|
||||
|
||||
@@ -181,6 +183,7 @@ export default function AdminLayout() {
|
||||
function resolveSelectedKey(pathname: string) {
|
||||
if (pathname.startsWith('/admin/orders')) return '/admin/orders'
|
||||
if (pathname.startsWith('/admin/tasks')) return '/admin/tasks'
|
||||
if (pathname.startsWith('/admin/kuaishou-industry')) return '/admin/kuaishou-industry'
|
||||
if (pathname === '/admin/platform-shops') return '/admin/platform-shops'
|
||||
return pathname
|
||||
}
|
||||
|
||||
@@ -0,0 +1,834 @@
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
ReloadOutlined,
|
||||
RollbackOutlined,
|
||||
SearchOutlined,
|
||||
SendOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
DatePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import PageHeader from '@/components/admin/PageHeader'
|
||||
import { showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
approveAdminKuaishouIndustryRefund,
|
||||
checkAdminKuaishouIndustryVoucherAvailable,
|
||||
consumeAdminKuaishouIndustryVoucher,
|
||||
disagreeAdminKuaishouIndustryRefund,
|
||||
fetchAdminKuaishouIndustryVouchers,
|
||||
listAdminKuaishouIndustryRefunds,
|
||||
resendAdminKuaishouIndustryVoucherCode,
|
||||
reverseAdminKuaishouIndustryVoucher,
|
||||
} from '@/services/admin'
|
||||
import type {
|
||||
AdminKuaishouIndustryOpenApiResult,
|
||||
AdminKuaishouIndustryRefundApprovePayload,
|
||||
AdminKuaishouIndustryRefundDisagreePayload,
|
||||
AdminKuaishouIndustryRefundListPayload,
|
||||
AdminKuaishouIndustryVoucher,
|
||||
AdminKuaishouIndustryVoucherToolPayload,
|
||||
} from '@/types/admin'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { stringifyDisplayJson } from '@/utils/date-time'
|
||||
|
||||
type DateRangeValue = [Dayjs | null, Dayjs | null] | null
|
||||
|
||||
type VoucherFilterState = {
|
||||
oid: string
|
||||
voucherCode: string
|
||||
taskId: string
|
||||
sellerId: string
|
||||
status: string
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
type RefundListState = {
|
||||
sellerId: string
|
||||
orderId: string
|
||||
type: string
|
||||
pageSize: string
|
||||
currentPage: string
|
||||
pcursor: string
|
||||
status: string
|
||||
negotiateStatus: string
|
||||
}
|
||||
|
||||
const DEFAULT_ETICKET_TYPE = 'DINING_OPEN_TICKET'
|
||||
|
||||
export default function AdminKuaishouIndustryPage() {
|
||||
const [voucherLoading, setVoucherLoading] = useState(false)
|
||||
const [actionLoading, setActionLoading] = useState('')
|
||||
const [vouchers, setVouchers] = useState<AdminKuaishouIndustryVoucher[]>([])
|
||||
const [voucherTotal, setVoucherTotal] = useState(0)
|
||||
const [voucherFilters, setVoucherFilters] = useState<VoucherFilterState>({
|
||||
oid: '',
|
||||
voucherCode: '',
|
||||
taskId: '',
|
||||
sellerId: '',
|
||||
status: '',
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
})
|
||||
const [toolForm, setToolForm] = useState<AdminKuaishouIndustryVoucherToolPayload>({
|
||||
eticketType: DEFAULT_ETICKET_TYPE,
|
||||
consumeType: 'delivery',
|
||||
})
|
||||
const [refundDateRange, setRefundDateRange] = useState<DateRangeValue>(() => [
|
||||
dayjs().subtract(1, 'day'),
|
||||
dayjs(),
|
||||
])
|
||||
const [refundListForm, setRefundListForm] = useState<RefundListState>({
|
||||
sellerId: '',
|
||||
orderId: '',
|
||||
type: '8',
|
||||
pageSize: '50',
|
||||
currentPage: '1',
|
||||
pcursor: '',
|
||||
status: '10',
|
||||
negotiateStatus: '',
|
||||
})
|
||||
const [approveForm, setApproveForm] = useState<AdminKuaishouIndustryRefundApprovePayload>({
|
||||
refundId: '',
|
||||
refundAmount: '',
|
||||
status: '',
|
||||
negotiateStatus: '',
|
||||
refundHandingWay: '10',
|
||||
})
|
||||
const [disagreeForm, setDisagreeForm] = useState<AdminKuaishouIndustryRefundDisagreePayload>({
|
||||
refundId: '',
|
||||
sellerDisagreeReason: '100',
|
||||
sellerDisagreeDesc: '',
|
||||
status: '10',
|
||||
negotiateStatus: '1',
|
||||
})
|
||||
const [lastResultTitle, setLastResultTitle] = useState('接口结果')
|
||||
const [lastResult, setLastResult] = useState<unknown>(null)
|
||||
const [refundRows, setRefundRows] = useState<Array<Record<string, unknown>>>([])
|
||||
|
||||
const columns = useMemo<TableColumnsType<AdminKuaishouIndustryVoucher>>(
|
||||
() => [
|
||||
{
|
||||
title: '券码',
|
||||
minWidth: 220,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text strong copyable>
|
||||
{row.voucherCode}
|
||||
</Typography.Text>
|
||||
<span className="muted">序号:{row.unitIndex}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '订单 / 任务',
|
||||
minWidth: 220,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text copyable>{row.oid || '-'}</Typography.Text>
|
||||
<span className="muted">
|
||||
任务:{row.taskId || '-'} / 订单:{row.orderId || '-'}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 150,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Tag color={getVoucherStatusColor(row.status)}>{getVoucherStatusLabel(row.status)}</Tag>
|
||||
<Tag color={row.sendCallbackStatus === 'success' ? 'green' : 'orange'}>
|
||||
发码:{row.sendCallbackStatus || '-'}
|
||||
</Tag>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '卖家',
|
||||
minWidth: 160,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<span>{row.sellerId || '-'}</span>
|
||||
<span className="muted">Token:{row.tokenMasked || '-'}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '核销',
|
||||
minWidth: 180,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<span>{row.consumeSerialNum || '-'}</span>
|
||||
<span className="muted">{formatAdminDateTime(row.consumedAt)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
width: 170,
|
||||
render: (_, row) => formatAdminDateTime(row.updatedAt),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
width: 120,
|
||||
render: (_, row) => (
|
||||
<Button size="small" type="primary" ghost onClick={() => selectVoucher(row)}>
|
||||
选中
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
||||
const refundColumns = useMemo<TableColumnsType<Record<string, unknown>>>(
|
||||
() => [
|
||||
{ title: '售后单', dataIndex: 'refundId', minWidth: 160 },
|
||||
{ title: '订单', dataIndex: 'oid', minWidth: 160 },
|
||||
{ title: '状态', dataIndex: 'status', width: 100 },
|
||||
{ title: '协商', dataIndex: 'negotiateStatus', width: 100 },
|
||||
{ title: '金额', dataIndex: 'refundFee', width: 100 },
|
||||
{
|
||||
title: '提交时间',
|
||||
minWidth: 170,
|
||||
render: (_, row) => formatTimestamp(row.submitTime || row.createTime),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
width: 120,
|
||||
render: (_, row) => (
|
||||
<Button size="small" onClick={() => fillRefundAction(row)}>
|
||||
填入
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
void loadVouchers()
|
||||
}, [])
|
||||
|
||||
async function loadVouchers(nextFilters: Partial<VoucherFilterState> = {}) {
|
||||
const filters = { ...voucherFilters, ...nextFilters }
|
||||
setVoucherFilters(filters)
|
||||
setVoucherLoading(true)
|
||||
|
||||
try {
|
||||
const response = await fetchAdminKuaishouIndustryVouchers({
|
||||
oid: filters.oid.trim(),
|
||||
voucherCode: filters.voucherCode.trim(),
|
||||
taskId: filters.taskId.trim(),
|
||||
sellerId: filters.sellerId.trim(),
|
||||
status: filters.status,
|
||||
page: filters.page,
|
||||
pageSize: filters.pageSize,
|
||||
})
|
||||
setVouchers(response.data.items)
|
||||
setVoucherTotal(response.data.pagination.total)
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '查询电子凭证失败')
|
||||
} finally {
|
||||
setVoucherLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
function selectVoucher(row: AdminKuaishouIndustryVoucher) {
|
||||
setToolForm((current) => ({
|
||||
...current,
|
||||
sellerId: row.sellerId,
|
||||
oid: row.oid,
|
||||
orderId: row.oid,
|
||||
taskId: row.taskId || '',
|
||||
voucherCode: row.voucherCode,
|
||||
serialNum: row.consumeSerialNum || current.serialNum,
|
||||
}))
|
||||
showSuccess('已填入券码操作区')
|
||||
}
|
||||
|
||||
async function runCheckAvailable() {
|
||||
await runOpenApiAction('检查电子凭证有效性', 'check', () =>
|
||||
checkAdminKuaishouIndustryVoucherAvailable(buildVoucherToolPayload()),
|
||||
)
|
||||
}
|
||||
|
||||
async function runReverse() {
|
||||
await runOpenApiAction('电子凭证冲正回调', 'reverse', () =>
|
||||
reverseAdminKuaishouIndustryVoucher({
|
||||
...buildVoucherToolPayload(),
|
||||
reason: toolForm.reason || '后台手动冲正',
|
||||
}),
|
||||
)
|
||||
await loadVouchers()
|
||||
}
|
||||
|
||||
async function runConsume() {
|
||||
setActionLoading('consume')
|
||||
try {
|
||||
const response = await consumeAdminKuaishouIndustryVoucher(buildVoucherToolPayload())
|
||||
setLastResultTitle('手动核销')
|
||||
setLastResult(response.data)
|
||||
showSuccess('手动核销已完成')
|
||||
await loadVouchers()
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '手动核销失败')
|
||||
} finally {
|
||||
setActionLoading('')
|
||||
}
|
||||
}
|
||||
|
||||
async function runResendCode() {
|
||||
setActionLoading('resend')
|
||||
try {
|
||||
const response = await resendAdminKuaishouIndustryVoucherCode(buildVoucherToolPayload())
|
||||
setLastResultTitle('重发发码回调')
|
||||
setLastResult(response.data)
|
||||
if (response.data.success) {
|
||||
showSuccess('发码回调已重发')
|
||||
} else {
|
||||
showError(response.data.error || '发码回调重发失败')
|
||||
}
|
||||
await loadVouchers()
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '重发发码回调失败')
|
||||
} finally {
|
||||
setActionLoading('')
|
||||
}
|
||||
}
|
||||
|
||||
async function runRefundList() {
|
||||
const [begin, end] = refundDateRange || []
|
||||
await runOpenApiAction('售后单列表', 'refund-list', () =>
|
||||
listAdminKuaishouIndustryRefunds({
|
||||
...refundListForm,
|
||||
beginTime: begin?.valueOf(),
|
||||
endTime: end?.valueOf(),
|
||||
} satisfies AdminKuaishouIndustryRefundListPayload),
|
||||
(result) => {
|
||||
const rows = extractRefundRows(result.response)
|
||||
setRefundRows(rows)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async function runApproveRefund() {
|
||||
await runOpenApiAction('同意退款', 'refund-approve', () =>
|
||||
approveAdminKuaishouIndustryRefund({
|
||||
...approveForm,
|
||||
sellerId: approveForm.sellerId || refundListForm.sellerId,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async function runDisagreeRefund() {
|
||||
await runOpenApiAction('不同意退款', 'refund-disagree', () =>
|
||||
disagreeAdminKuaishouIndustryRefund({
|
||||
...disagreeForm,
|
||||
sellerId: disagreeForm.sellerId || refundListForm.sellerId,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async function runOpenApiAction(
|
||||
title: string,
|
||||
loadingKey: string,
|
||||
action: () => Promise<{ data: AdminKuaishouIndustryOpenApiResult }>,
|
||||
afterSuccess?: (result: AdminKuaishouIndustryOpenApiResult) => void,
|
||||
) {
|
||||
setActionLoading(loadingKey)
|
||||
try {
|
||||
const response = await action()
|
||||
setLastResultTitle(title)
|
||||
setLastResult(response.data)
|
||||
afterSuccess?.(response.data)
|
||||
if (response.data.success) {
|
||||
showSuccess(`${title}已执行`)
|
||||
} else {
|
||||
showError(response.data.error || `${title}返回失败`)
|
||||
}
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : `${title}失败`)
|
||||
} finally {
|
||||
setActionLoading('')
|
||||
}
|
||||
}
|
||||
|
||||
function buildVoucherToolPayload(): AdminKuaishouIndustryVoucherToolPayload {
|
||||
const voucherCode = String(toolForm.voucherCode || '').trim()
|
||||
return {
|
||||
...toolForm,
|
||||
etickets: voucherCode ? [{ id: voucherCode, code: voucherCode, num: 1 }] : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function fillRefundAction(row: Record<string, unknown>) {
|
||||
const refundId = String(row.refundId || '').trim()
|
||||
const status = String(row.status || '').trim()
|
||||
const negotiateStatus = String(row.negotiateStatus || '').trim()
|
||||
setApproveForm((current) => ({
|
||||
...current,
|
||||
refundId,
|
||||
status,
|
||||
negotiateStatus,
|
||||
refundAmount: String(row.refundFee || current.refundAmount || ''),
|
||||
}))
|
||||
setDisagreeForm((current) => ({
|
||||
...current,
|
||||
refundId,
|
||||
status,
|
||||
negotiateStatus: negotiateStatus || current.negotiateStatus,
|
||||
}))
|
||||
showSuccess('已填入退款操作区')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-stack kuaishou-industry-page">
|
||||
<PageHeader title="快手电子凭证" description="行业电子凭证接口工具与售后处理" />
|
||||
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
key: 'vouchers',
|
||||
label: '券码操作',
|
||||
children: renderVoucherTab(),
|
||||
},
|
||||
{
|
||||
key: 'refunds',
|
||||
label: '售后退款',
|
||||
children: renderRefundTab(),
|
||||
},
|
||||
{
|
||||
key: 'result',
|
||||
label: '接口结果',
|
||||
children: renderResultTab(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
function renderVoucherTab() {
|
||||
return (
|
||||
<div className="page-stack">
|
||||
<Card>
|
||||
<Space wrap className="filter-form">
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="订单号 oid"
|
||||
value={voucherFilters.oid}
|
||||
onChange={(event) => setVoucherFilters({ ...voucherFilters, oid: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="券码"
|
||||
value={voucherFilters.voucherCode}
|
||||
onChange={(event) =>
|
||||
setVoucherFilters({ ...voucherFilters, voucherCode: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="任务 ID"
|
||||
value={voucherFilters.taskId}
|
||||
onChange={(event) =>
|
||||
setVoucherFilters({ ...voucherFilters, taskId: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
allowClear
|
||||
placeholder="卖家 ID"
|
||||
value={voucherFilters.sellerId}
|
||||
onChange={(event) =>
|
||||
setVoucherFilters({ ...voucherFilters, sellerId: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="券码状态"
|
||||
style={{ width: 132 }}
|
||||
value={voucherFilters.status || undefined}
|
||||
options={[
|
||||
{ label: '未使用', value: 'UNUSED' },
|
||||
{ label: '已核销', value: 'CONSUMED' },
|
||||
{ label: '已销毁', value: 'DESTROYED' },
|
||||
]}
|
||||
onChange={(value) => setVoucherFilters({ ...voucherFilters, status: value || '' })}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
loading={voucherLoading}
|
||||
onClick={() => loadVouchers({ page: 1 })}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<div className="kuaishou-industry-split">
|
||||
<Card title="本地券码">
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={vouchers}
|
||||
loading={voucherLoading}
|
||||
scroll={{ x: 1180 }}
|
||||
pagination={{
|
||||
current: voucherFilters.page,
|
||||
pageSize: voucherFilters.pageSize,
|
||||
total: voucherTotal,
|
||||
showSizeChanger: true,
|
||||
onChange: (page, pageSize) => loadVouchers({ page, pageSize }),
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card title="接口操作">
|
||||
<div className="kuaishou-industry-tool-grid">
|
||||
<Input
|
||||
placeholder="卖家 ID"
|
||||
value={toolForm.sellerId}
|
||||
onChange={(event) => updateToolForm({ sellerId: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="订单号 oid"
|
||||
value={toolForm.oid}
|
||||
onChange={(event) =>
|
||||
updateToolForm({ oid: event.target.value, orderId: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="任务 ID"
|
||||
value={String(toolForm.taskId || '')}
|
||||
onChange={(event) => updateToolForm({ taskId: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="券码"
|
||||
value={toolForm.voucherCode}
|
||||
onChange={(event) => updateToolForm({ voucherCode: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="电子凭证类型"
|
||||
value={toolForm.eticketType}
|
||||
onChange={(event) => updateToolForm({ eticketType: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="核销序列号"
|
||||
value={toolForm.serialNum}
|
||||
onChange={(event) => updateToolForm({ serialNum: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="核销类型"
|
||||
value={toolForm.consumeType}
|
||||
onChange={(event) => updateToolForm({ consumeType: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="冲正原因"
|
||||
value={toolForm.reason}
|
||||
onChange={(event) => updateToolForm({ reason: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="门店名称"
|
||||
value={toolForm.storeName}
|
||||
onChange={(event) => updateToolForm({ storeName: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="门店地址"
|
||||
value={toolForm.storeAddress}
|
||||
onChange={(event) => updateToolForm({ storeAddress: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<Space wrap className="kuaishou-industry-actions">
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
loading={actionLoading === 'check'}
|
||||
onClick={runCheckAvailable}
|
||||
>
|
||||
检查有效性
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
loading={actionLoading === 'consume'}
|
||||
onClick={runConsume}
|
||||
>
|
||||
手动核销
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
icon={<RollbackOutlined />}
|
||||
loading={actionLoading === 'reverse'}
|
||||
onClick={runReverse}
|
||||
>
|
||||
冲正
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SendOutlined />}
|
||||
loading={actionLoading === 'resend'}
|
||||
onClick={runResendCode}
|
||||
>
|
||||
重发发码
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function renderRefundTab() {
|
||||
return (
|
||||
<div className="page-stack">
|
||||
<Card title="售后单列表">
|
||||
<Space wrap className="filter-form">
|
||||
<Input
|
||||
placeholder="卖家 ID"
|
||||
value={refundListForm.sellerId}
|
||||
onChange={(event) => updateRefundListForm({ sellerId: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="订单号"
|
||||
value={refundListForm.orderId}
|
||||
onChange={(event) => updateRefundListForm({ orderId: event.target.value })}
|
||||
/>
|
||||
<DatePicker.RangePicker
|
||||
showTime
|
||||
value={refundDateRange}
|
||||
onChange={(value) => setRefundDateRange(value)}
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
value={refundListForm.type}
|
||||
options={[
|
||||
{ label: '等待退款', value: '8' },
|
||||
{ label: '全部退款', value: '9' },
|
||||
]}
|
||||
onChange={(value) => updateRefundListForm({ type: value })}
|
||||
/>
|
||||
<Input placeholder="状态" value={refundListForm.status} onChange={(event) => updateRefundListForm({ status: event.target.value })} />
|
||||
<Input placeholder="游标" value={refundListForm.pcursor} onChange={(event) => updateRefundListForm({ pcursor: event.target.value })} />
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
value={Number(refundListForm.pageSize)}
|
||||
onChange={(value) => updateRefundListForm({ pageSize: String(value || 50) })}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
loading={actionLoading === 'refund-list'}
|
||||
onClick={runRefundList}
|
||||
>
|
||||
查询售后
|
||||
</Button>
|
||||
</Space>
|
||||
<Table
|
||||
rowKey={(row) => String(row.refundId || row.oid || JSON.stringify(row))}
|
||||
columns={refundColumns}
|
||||
dataSource={refundRows}
|
||||
scroll={{ x: 980 }}
|
||||
pagination={{ pageSize: 10 }}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<div className="kuaishou-industry-split">
|
||||
<Card title="同意退款">
|
||||
<div className="kuaishou-industry-tool-grid">
|
||||
<Input
|
||||
placeholder="卖家 ID"
|
||||
value={approveForm.sellerId || refundListForm.sellerId}
|
||||
onChange={(event) => setApproveForm({ ...approveForm, sellerId: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="退款单编号"
|
||||
value={String(approveForm.refundId || '')}
|
||||
onChange={(event) => setApproveForm({ ...approveForm, refundId: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="退款金额(分)"
|
||||
value={String(approveForm.refundAmount || '')}
|
||||
onChange={(event) =>
|
||||
setApproveForm({ ...approveForm, refundAmount: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="退款单状态"
|
||||
value={String(approveForm.status || '')}
|
||||
onChange={(event) => setApproveForm({ ...approveForm, status: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder="协商状态"
|
||||
value={String(approveForm.negotiateStatus || '')}
|
||||
onChange={(event) =>
|
||||
setApproveForm({ ...approveForm, negotiateStatus: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="退款方式"
|
||||
value={String(approveForm.refundHandingWay || '')}
|
||||
onChange={(event) =>
|
||||
setApproveForm({ ...approveForm, refundHandingWay: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="说明"
|
||||
value={approveForm.desc}
|
||||
onChange={(event) => setApproveForm({ ...approveForm, desc: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
loading={actionLoading === 'refund-approve'}
|
||||
onClick={runApproveRefund}
|
||||
>
|
||||
同意退款
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
<Card title="不同意退款">
|
||||
<div className="kuaishou-industry-tool-grid">
|
||||
<Input
|
||||
placeholder="卖家 ID"
|
||||
value={disagreeForm.sellerId || refundListForm.sellerId}
|
||||
onChange={(event) =>
|
||||
setDisagreeForm({ ...disagreeForm, sellerId: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="退款单编号"
|
||||
value={String(disagreeForm.refundId || '')}
|
||||
onChange={(event) =>
|
||||
setDisagreeForm({ ...disagreeForm, refundId: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="拒绝原因枚举"
|
||||
value={String(disagreeForm.sellerDisagreeReason || '')}
|
||||
onChange={(event) =>
|
||||
setDisagreeForm({ ...disagreeForm, sellerDisagreeReason: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="拒绝说明"
|
||||
value={disagreeForm.sellerDisagreeDesc}
|
||||
onChange={(event) =>
|
||||
setDisagreeForm({ ...disagreeForm, sellerDisagreeDesc: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="退款单状态"
|
||||
value={String(disagreeForm.status || '')}
|
||||
onChange={(event) =>
|
||||
setDisagreeForm({ ...disagreeForm, status: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="协商状态"
|
||||
value={String(disagreeForm.negotiateStatus || '')}
|
||||
onChange={(event) =>
|
||||
setDisagreeForm({ ...disagreeForm, negotiateStatus: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
danger
|
||||
icon={<RollbackOutlined />}
|
||||
loading={actionLoading === 'refund-disagree'}
|
||||
onClick={runDisagreeRefund}
|
||||
>
|
||||
不同意退款
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function renderResultTab() {
|
||||
return (
|
||||
<Card
|
||||
title={lastResultTitle}
|
||||
extra={
|
||||
<Button icon={<ReloadOutlined />} onClick={() => setLastResult(null)}>
|
||||
清空
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{lastResult ? (
|
||||
<pre className="json-preview">{stringifyDisplayJson(lastResult)}</pre>
|
||||
) : (
|
||||
<Alert type="info" showIcon message="暂无接口结果" />
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function updateToolForm(patch: Partial<AdminKuaishouIndustryVoucherToolPayload>) {
|
||||
setToolForm((current) => ({ ...current, ...patch }))
|
||||
}
|
||||
|
||||
function updateRefundListForm(patch: Partial<RefundListState>) {
|
||||
setRefundListForm((current) => ({ ...current, ...patch }))
|
||||
}
|
||||
}
|
||||
|
||||
function extractRefundRows(response: Record<string, unknown> | null): Array<Record<string, unknown>> {
|
||||
const data = response?.data
|
||||
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const rows = (data as { refundOrderInfoList?: unknown }).refundOrderInfoList
|
||||
return Array.isArray(rows)
|
||||
? rows.filter((item): item is Record<string, unknown> =>
|
||||
Boolean(item && typeof item === 'object' && !Array.isArray(item)),
|
||||
)
|
||||
: []
|
||||
}
|
||||
|
||||
function getVoucherStatusColor(status: string) {
|
||||
const normalized = String(status || '').toUpperCase()
|
||||
if (normalized === 'CONSUMED') return 'green'
|
||||
if (normalized === 'DESTROYED') return 'red'
|
||||
return 'blue'
|
||||
}
|
||||
|
||||
function getVoucherStatusLabel(status: string) {
|
||||
const normalized = String(status || '').toUpperCase()
|
||||
if (normalized === 'CONSUMED') return '已核销'
|
||||
if (normalized === 'DESTROYED') return '已销毁'
|
||||
if (normalized === 'UNUSED') return '未使用'
|
||||
return status || '-'
|
||||
}
|
||||
|
||||
function formatTimestamp(value: unknown) {
|
||||
const timestamp = Number(value || 0)
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return formatAdminDateTime(new Date(timestamp).toISOString())
|
||||
}
|
||||
@@ -3,5 +3,6 @@ export * from './dashboard'
|
||||
export * from './users'
|
||||
export * from './audit-logs'
|
||||
export * from './platform-config'
|
||||
export * from './kuaishou-industry'
|
||||
export * from './orders'
|
||||
export * from './tasks'
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { apiGet, apiPost } from '@/lib/http'
|
||||
import type {
|
||||
AdminKuaishouIndustryOpenApiResult,
|
||||
AdminKuaishouIndustryRefundApprovePayload,
|
||||
AdminKuaishouIndustryRefundDisagreePayload,
|
||||
AdminKuaishouIndustryRefundListPayload,
|
||||
AdminKuaishouIndustryVoucherConsumeResult,
|
||||
AdminKuaishouIndustryVoucherListResult,
|
||||
AdminKuaishouIndustryVoucherResendResult,
|
||||
AdminKuaishouIndustryVoucherToolPayload,
|
||||
} from '@/types/admin'
|
||||
|
||||
export function fetchAdminKuaishouIndustryVouchers(params?: Record<string, unknown>) {
|
||||
return apiGet<AdminKuaishouIndustryVoucherListResult>(
|
||||
'/api/v1/admin/kuaishou-industry/vouchers',
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
export function listAdminKuaishouIndustryRefunds(
|
||||
payload: AdminKuaishouIndustryRefundListPayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryOpenApiResult>(
|
||||
'/api/v1/admin/kuaishou-industry/refunds/list',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function approveAdminKuaishouIndustryRefund(
|
||||
payload: AdminKuaishouIndustryRefundApprovePayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryOpenApiResult>(
|
||||
'/api/v1/admin/kuaishou-industry/refunds/approve',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function disagreeAdminKuaishouIndustryRefund(
|
||||
payload: AdminKuaishouIndustryRefundDisagreePayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryOpenApiResult>(
|
||||
'/api/v1/admin/kuaishou-industry/refunds/disagree',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function checkAdminKuaishouIndustryVoucherAvailable(
|
||||
payload: AdminKuaishouIndustryVoucherToolPayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryOpenApiResult>(
|
||||
'/api/v1/admin/kuaishou-industry/vouchers/check-available',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function reverseAdminKuaishouIndustryVoucher(
|
||||
payload: AdminKuaishouIndustryVoucherToolPayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryOpenApiResult>(
|
||||
'/api/v1/admin/kuaishou-industry/vouchers/reverse',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function consumeAdminKuaishouIndustryVoucher(
|
||||
payload: AdminKuaishouIndustryVoucherToolPayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryVoucherConsumeResult>(
|
||||
'/api/v1/admin/kuaishou-industry/vouchers/consume',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function resendAdminKuaishouIndustryVoucherCode(
|
||||
payload: AdminKuaishouIndustryVoucherToolPayload,
|
||||
) {
|
||||
return apiPost<AdminKuaishouIndustryVoucherResendResult>(
|
||||
'/api/v1/admin/kuaishou-industry/vouchers/resend-code',
|
||||
payload,
|
||||
)
|
||||
}
|
||||
@@ -306,6 +306,24 @@ select {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.kuaishou-industry-split {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.45fr) minmax(360px, 0.75fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.kuaishou-industry-tool-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.kuaishou-industry-actions {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.task-action-hint {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
@@ -1083,6 +1101,10 @@ select {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.kuaishou-industry-split {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.manual-dispatch-grid,
|
||||
.fulfillment-overview-grid,
|
||||
.task-flow-grid {
|
||||
|
||||
@@ -28,6 +28,18 @@ export type {
|
||||
AdminTaskDetail,
|
||||
} from './tasks'
|
||||
|
||||
export type {
|
||||
AdminKuaishouIndustryOpenApiResult,
|
||||
AdminKuaishouIndustryVoucher,
|
||||
AdminKuaishouIndustryVoucherListResult,
|
||||
AdminKuaishouIndustryVoucherConsumeResult,
|
||||
AdminKuaishouIndustryVoucherResendResult,
|
||||
AdminKuaishouIndustryRefundListPayload,
|
||||
AdminKuaishouIndustryRefundApprovePayload,
|
||||
AdminKuaishouIndustryRefundDisagreePayload,
|
||||
AdminKuaishouIndustryVoucherToolPayload,
|
||||
} from './kuaishou-industry'
|
||||
|
||||
// Platform config types
|
||||
export type {
|
||||
AdminNotificationBarkRecipient,
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import type { AdminPagination } from './common'
|
||||
|
||||
export interface AdminKuaishouIndustryOpenApiResult {
|
||||
success: boolean
|
||||
response: Record<string, unknown> | null
|
||||
error: string
|
||||
request: Record<string, unknown> | null
|
||||
durationMs: number
|
||||
httpStatus: number
|
||||
skippedReason: string
|
||||
voucher?: AdminKuaishouIndustryVoucher | null
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryVoucher {
|
||||
id: number
|
||||
voucherCode: string
|
||||
oid: string
|
||||
orderId: number | null
|
||||
taskId: number | null
|
||||
unitIndex: number
|
||||
sellerId: string
|
||||
tokenMasked: string
|
||||
status: string
|
||||
validStartTime: number
|
||||
validEndTime: number
|
||||
consumeSerialNum: string
|
||||
consumeDetails: Array<Record<string, unknown>>
|
||||
consumedAt: string | null
|
||||
destroyedAt: string | null
|
||||
sendCallbackStatus: string
|
||||
sendCallbackAttemptCount: number
|
||||
sendCallbackLastError: string
|
||||
sendCallbackSentAt: string | null
|
||||
rawPayload: Record<string, unknown>
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryVoucherListResult {
|
||||
items: AdminKuaishouIndustryVoucher[]
|
||||
pagination: AdminPagination
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryVoucherConsumeResult {
|
||||
success: boolean
|
||||
voucher: AdminKuaishouIndustryVoucher
|
||||
task: null | {
|
||||
taskId: number
|
||||
taskNo: string
|
||||
status: string
|
||||
deliveryStatus: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryVoucherResendResult {
|
||||
success: boolean
|
||||
response: Record<string, unknown> | null
|
||||
error: string
|
||||
voucher: AdminKuaishouIndustryVoucher | null
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryRefundListPayload {
|
||||
sellerId?: string
|
||||
beginTime?: number | string
|
||||
endTime?: number | string
|
||||
type?: number | string
|
||||
pageSize?: number | string
|
||||
currentPage?: number | string
|
||||
sort?: number | string
|
||||
queryType?: number | string
|
||||
negotiateStatus?: number | string
|
||||
pcursor?: string
|
||||
status?: number | string
|
||||
orderId?: string
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryRefundApprovePayload {
|
||||
sellerId?: string
|
||||
refundId?: number | string
|
||||
desc?: string
|
||||
refundAmount?: number | string
|
||||
status?: number | string
|
||||
negotiateStatus?: number | string
|
||||
refundHandingWay?: number | string
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryRefundDisagreePayload {
|
||||
sellerId?: string
|
||||
refundId?: number | string
|
||||
sellerDisagreeReason?: number | string
|
||||
sellerDisagreeDesc?: string
|
||||
sellerDisagreeImages?: string[]
|
||||
status?: number | string
|
||||
negotiateStatus?: number | string
|
||||
}
|
||||
|
||||
export interface AdminKuaishouIndustryVoucherToolPayload {
|
||||
sellerId?: string
|
||||
buyerId?: string
|
||||
orderId?: string
|
||||
oid?: string
|
||||
taskId?: number | string
|
||||
voucherCode?: string
|
||||
eticketType?: string
|
||||
consumeType?: string
|
||||
serialNum?: string
|
||||
reason?: string
|
||||
token?: string
|
||||
storeName?: string
|
||||
storeAddress?: string
|
||||
expressCode?: string
|
||||
expressNo?: string
|
||||
etickets?: Array<{ id?: string; code?: string; num?: number | string }>
|
||||
}
|
||||
Reference in New Issue
Block a user