统一前后端代码格式化配置
This commit is contained in:
@@ -128,7 +128,11 @@ export default function AdminAuditLogsPage() {
|
||||
<PageHeader
|
||||
title="操作审计"
|
||||
description="集中查看高风险后台动作,便于排查谁在什么时间改了什么。"
|
||||
extra={<Typography.Text type="secondary">共 {data?.pagination.total || 0} 条审计记录</Typography.Text>}
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
共 {data?.pagination.total || 0} 条审计记录
|
||||
</Typography.Text>
|
||||
}
|
||||
/>
|
||||
|
||||
{!isAdmin ? (
|
||||
|
||||
@@ -332,7 +332,12 @@ export default function AdminCloudtentaclesRecordsPage() {
|
||||
<Button icon={<ReloadOutlined />} onClick={resetFilters}>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" icon={<SearchOutlined />} loading={loading} onClick={() => loadRecords(1)}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
loading={loading}
|
||||
onClick={() => loadRecords(1)}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -555,7 +560,10 @@ function drawTicketCard(
|
||||
drawEllipsisText(ctx, `账号:${record.fulfillUser || record.phone || '-'}`, contentX, y + 90, 250)
|
||||
}
|
||||
|
||||
function drawRecordMeta(ctx: CanvasRenderingContext2D, record: AdminCloudtentaclesDeliveryRecordItem) {
|
||||
function drawRecordMeta(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
record: AdminCloudtentaclesDeliveryRecordItem,
|
||||
) {
|
||||
const left = 80
|
||||
const top = 250
|
||||
const lineHeight = 22
|
||||
|
||||
@@ -24,10 +24,7 @@ import StatusTag from '@/components/admin/StatusTag'
|
||||
import dayjs from '@/lib/dayjs'
|
||||
import { fetchAdminDashboardSummary, fetchAdminLoginLogs } from '@/services/admin'
|
||||
import type { AdminLoginLogItem } from '@/types/admin'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
const cards = [
|
||||
@@ -98,11 +95,7 @@ export default function AdminDashboardPage() {
|
||||
title: '结果',
|
||||
width: 90,
|
||||
render: (_, row) =>
|
||||
row.success ? (
|
||||
<Tag color="green">成功</Tag>
|
||||
) : (
|
||||
<Tag color="red">失败</Tag>
|
||||
),
|
||||
row.success ? <Tag color="green">成功</Tag> : <Tag color="red">失败</Tag>,
|
||||
},
|
||||
{
|
||||
title: '登录地点',
|
||||
@@ -132,8 +125,7 @@ export default function AdminDashboardPage() {
|
||||
const range = Array.isArray(values.dateRange) ? values.dateRange : []
|
||||
const dateFrom =
|
||||
range[0] && dayjs(range[0]).isValid() ? dayjs(range[0]).format('YYYY-MM-DD') : ''
|
||||
const dateTo =
|
||||
range[1] && dayjs(range[1]).isValid() ? dayjs(range[1]).format('YYYY-MM-DD') : ''
|
||||
const dateTo = range[1] && dayjs(range[1]).isValid() ? dayjs(range[1]).format('YYYY-MM-DD') : ''
|
||||
setPage(1)
|
||||
setFilters({
|
||||
username: String(values.username || '').trim(),
|
||||
@@ -223,9 +215,7 @@ export default function AdminDashboardPage() {
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
message={
|
||||
loginQuery.error instanceof Error
|
||||
? loginQuery.error.message
|
||||
: '读取登录记录失败'
|
||||
loginQuery.error instanceof Error ? loginQuery.error.message : '读取登录记录失败'
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -102,10 +102,7 @@ export default function AdminDevMockPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const defaultOrderNo = useMemo(
|
||||
() => lastCreate?.orderNo || '',
|
||||
[lastCreate?.orderNo],
|
||||
)
|
||||
const defaultOrderNo = useMemo(() => lastCreate?.orderNo || '', [lastCreate?.orderNo])
|
||||
|
||||
if (statusQuery.isLoading) {
|
||||
return (
|
||||
@@ -138,9 +135,7 @@ export default function AdminDevMockPage() {
|
||||
title="开发 Mock"
|
||||
description="一键造单、测领取页与 91 查单,无需真实 91 / 快手 / feifei 环境。"
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
NODE_ENV={status?.nodeEnv || '-'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">NODE_ENV={status?.nodeEnv || '-'}</Typography.Text>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -120,8 +120,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
const [actionLoading, setActionLoading] = useState('')
|
||||
const [consumingVoucherId, setConsumingVoucherId] = useState<number | null>(null)
|
||||
const [vouchers, setVouchers] = useState<AdminKuaishouIndustryVoucher[]>([])
|
||||
const [selectedVoucher, setSelectedVoucher] =
|
||||
useState<AdminKuaishouIndustryVoucher | null>(null)
|
||||
const [selectedVoucher, setSelectedVoucher] = useState<AdminKuaishouIndustryVoucher | null>(null)
|
||||
const [voucherTotal, setVoucherTotal] = useState(0)
|
||||
const [voucherFilters, setVoucherFilters] = useState<VoucherFilterState>({
|
||||
oid: '',
|
||||
@@ -247,7 +246,10 @@ export default function AdminKuaishouIndustryPage() {
|
||||
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text copyable={Boolean(row.oid)} ellipsis={{ tooltip: row.oid || undefined }}>
|
||||
<Typography.Text
|
||||
copyable={Boolean(row.oid)}
|
||||
ellipsis={{ tooltip: row.oid || undefined }}
|
||||
>
|
||||
{row.oid || '-'}
|
||||
</Typography.Text>
|
||||
{productLabel ? (
|
||||
@@ -277,7 +279,11 @@ export default function AdminKuaishouIndustryPage() {
|
||||
发码:{row.sendCallbackStatus || '-'}
|
||||
</Tag>
|
||||
{row.eticketType ? (
|
||||
<Typography.Text type="secondary" ellipsis={{ tooltip: row.eticketType }} className="muted">
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
ellipsis={{ tooltip: row.eticketType }}
|
||||
className="muted"
|
||||
>
|
||||
{row.eticketType}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
@@ -306,7 +312,9 @@ export default function AdminKuaishouIndustryPage() {
|
||||
const consumedAt = formatAdminDateTime(row.consumedAt)
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<span title={serial || undefined}>{consumedAt !== '-' ? consumedAt : serial || '-'}</span>
|
||||
<span title={serial || undefined}>
|
||||
{consumedAt !== '-' ? consumedAt : serial || '-'}
|
||||
</span>
|
||||
{serial && consumedAt !== '-' ? (
|
||||
<Typography.Text type="secondary" ellipsis={{ tooltip: serial }} className="muted">
|
||||
{serial}
|
||||
@@ -493,9 +501,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
setActionLoading('consume')
|
||||
try {
|
||||
const response = await consumeAdminKuaishouIndustryVoucher(buildVoucherToolPayload())
|
||||
publishActionResult(
|
||||
buildIndustryActionResultView('consume', '手动核销', response.data),
|
||||
)
|
||||
publishActionResult(buildIndustryActionResultView('consume', '手动核销', response.data))
|
||||
showSuccess('手动核销已完成')
|
||||
await loadVouchers()
|
||||
} catch (error) {
|
||||
@@ -533,9 +539,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
etickets: [{ id: row.voucherCode, code: row.voucherCode, num: 1 }],
|
||||
})
|
||||
if (!isSupportOnly) {
|
||||
publishActionResult(
|
||||
buildIndustryActionResultView('consume', '手动核销', response.data),
|
||||
)
|
||||
publishActionResult(buildIndustryActionResultView('consume', '手动核销', response.data))
|
||||
}
|
||||
showSuccess(`券码 ${row.voucherCode} 已核销`)
|
||||
await loadVouchers()
|
||||
@@ -560,9 +564,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
setActionLoading('resend')
|
||||
try {
|
||||
const response = await resendAdminKuaishouIndustryVoucherCode(buildVoucherToolPayload())
|
||||
publishActionResult(
|
||||
buildIndustryActionResultView('resend', '重发发码回调', response.data),
|
||||
)
|
||||
publishActionResult(buildIndustryActionResultView('resend', '重发发码回调', response.data))
|
||||
if (response.data.success) {
|
||||
showSuccess('发码回调已重发')
|
||||
} else {
|
||||
@@ -590,9 +592,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
...buildVoucherToolPayload(),
|
||||
reason: toolForm.reason || 'USER_APPLY_REFUND',
|
||||
})
|
||||
publishActionResult(
|
||||
buildIndustryActionResultView('destroy', '手动销毁', response.data),
|
||||
)
|
||||
publishActionResult(buildIndustryActionResultView('destroy', '手动销毁', response.data))
|
||||
showSuccess(
|
||||
response.data.alreadyDestroyed
|
||||
? `券码已是销毁状态,销毁回调已重试(${response.data.reason || 'USER_APPLY_REFUND'})`
|
||||
@@ -677,9 +677,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
title: string,
|
||||
loadingKey: string,
|
||||
action: () => Promise<{ data: AdminKuaishouIndustryOpenApiResult }>,
|
||||
afterSuccess?: (
|
||||
result: AdminKuaishouIndustryOpenApiResult,
|
||||
) => { refundCount?: number } | void,
|
||||
afterSuccess?: (result: AdminKuaishouIndustryOpenApiResult) => { refundCount?: number } | void,
|
||||
) {
|
||||
setActionLoading(loadingKey)
|
||||
try {
|
||||
@@ -1069,9 +1067,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
items={[
|
||||
{
|
||||
key: 'manual-target',
|
||||
label: selectedVoucher
|
||||
? '手动指定卡券(清除当前选择后可用)'
|
||||
: '手动指定卡券',
|
||||
label: selectedVoucher ? '手动指定卡券(清除当前选择后可用)' : '手动指定卡券',
|
||||
children: (
|
||||
<div className="kuaishou-industry-tool-grid">
|
||||
<label className="kuaishou-industry-tool-field">
|
||||
@@ -1108,9 +1104,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
<Input
|
||||
disabled={Boolean(selectedVoucher)}
|
||||
value={toolForm.voucherCode}
|
||||
onChange={(event) =>
|
||||
updateToolForm({ voucherCode: event.target.value })
|
||||
}
|
||||
onChange={(event) => updateToolForm({ voucherCode: event.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="kuaishou-industry-tool-field kuaishou-industry-tool-field-wide">
|
||||
@@ -1118,9 +1112,7 @@ export default function AdminKuaishouIndustryPage() {
|
||||
<Input
|
||||
disabled={Boolean(selectedVoucher)}
|
||||
value={toolForm.eticketType}
|
||||
onChange={(event) =>
|
||||
updateToolForm({ eticketType: event.target.value })
|
||||
}
|
||||
onChange={(event) => updateToolForm({ eticketType: event.target.value })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -1568,11 +1560,9 @@ function renderShopCell(
|
||||
return <span>-</span>
|
||||
}
|
||||
|
||||
const tipParts = [
|
||||
name || null,
|
||||
id ? `ID:${id}` : null,
|
||||
token ? `Token:${token}` : null,
|
||||
].filter(Boolean)
|
||||
const tipParts = [name || null, id ? `ID:${id}` : null, token ? `Token:${token}` : null].filter(
|
||||
Boolean,
|
||||
)
|
||||
const tip = tipParts.join('\n')
|
||||
|
||||
if (compact) {
|
||||
@@ -1671,11 +1661,7 @@ function buildIndustryActionResultView(
|
||||
'展示状态',
|
||||
pickFirstString([etickets[0].displayStatus, etickets[0].status]) || '-',
|
||||
)
|
||||
pushHighlight(
|
||||
highlights,
|
||||
'平台类型',
|
||||
pickFirstString([etickets[0].eticketType]) || '-',
|
||||
)
|
||||
pushHighlight(highlights, '平台类型', pickFirstString([etickets[0].eticketType]) || '-')
|
||||
pushHighlight(highlights, '剩余数量', String(etickets[0].leftNum ?? etickets[0].num ?? 1))
|
||||
}
|
||||
}
|
||||
@@ -1689,11 +1675,7 @@ function buildIndustryActionResultView(
|
||||
) {
|
||||
if (voucher) {
|
||||
pushHighlight(highlights, '券码', String(voucher.voucherCode || '-'))
|
||||
pushHighlight(
|
||||
highlights,
|
||||
'券状态',
|
||||
getVoucherStatusLabel(String(voucher.status || '')),
|
||||
)
|
||||
pushHighlight(highlights, '券状态', getVoucherStatusLabel(String(voucher.status || '')))
|
||||
if (voucher.eticketType) {
|
||||
pushHighlight(highlights, '凭证类型', String(voucher.eticketType))
|
||||
}
|
||||
@@ -1724,9 +1706,7 @@ function buildIndustryActionResultView(
|
||||
|
||||
if (kind === 'refund-list') {
|
||||
const count =
|
||||
typeof meta?.refundCount === 'number'
|
||||
? meta.refundCount
|
||||
: extractRefundRows(response).length
|
||||
typeof meta?.refundCount === 'number' ? meta.refundCount : extractRefundRows(response).length
|
||||
pushHighlight(highlights, '售后单数量', String(count))
|
||||
const pcursor = pickFirstString([
|
||||
responseData?.pcursor,
|
||||
@@ -1739,10 +1719,7 @@ function buildIndustryActionResultView(
|
||||
}
|
||||
|
||||
if (kind === 'refund-approve' || kind === 'refund-disagree') {
|
||||
const refundId = pickFirstString([
|
||||
responseData?.refundId,
|
||||
asRecord(openApi.request)?.refundId,
|
||||
])
|
||||
const refundId = pickFirstString([responseData?.refundId, asRecord(openApi.request)?.refundId])
|
||||
if (refundId) {
|
||||
pushHighlight(highlights, '退款单号', refundId)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ export default function AdminOrdersPage() {
|
||||
minWidth: 180,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<strong>{row.totalAmount || '0.00'} {row.currency}</strong>
|
||||
<strong>
|
||||
{row.totalAmount || '0.00'} {row.currency}
|
||||
</strong>
|
||||
<span className="muted">{formatAdminDateTime(row.createdAt)}</span>
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -102,9 +102,7 @@ export default function AdminTaskDetailPage() {
|
||||
return lastClaimUrl || detail?.claimToken?.claimUrl || ''
|
||||
}, [detail?.claimToken?.claimUrl, detail?.claimToken?.status, detail?.task.status, lastClaimUrl])
|
||||
const claimLinkInvalid = Boolean(
|
||||
detail?.claimToken?.claimUrl &&
|
||||
claimTokenStatus &&
|
||||
!isUsableClaimLinkStatus(claimTokenStatus),
|
||||
detail?.claimToken?.claimUrl && claimTokenStatus && !isUsableClaimLinkStatus(claimTokenStatus),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -364,7 +362,8 @@ export default function AdminTaskDetailPage() {
|
||||
{resolvedDetail.orderItem?.skuName || resolvedDetail.task.skuName || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="角色">
|
||||
{resolvedDetail.task.roleName || '-'} {resolvedDetail.task.roleId ? `(${resolvedDetail.task.roleId})` : ''}
|
||||
{resolvedDetail.task.roleName || '-'}{' '}
|
||||
{resolvedDetail.task.roleId ? `(${resolvedDetail.task.roleId})` : ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">
|
||||
{formatAdminDateTime(resolvedDetail.task.updatedAt)}
|
||||
@@ -654,7 +653,10 @@ function TaskActionPanel({
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{!canManageTaskLifecycle && !canOperateIndustryVoucher && !canCloseTasks && !hasCloudActions ? (
|
||||
{!canManageTaskLifecycle &&
|
||||
!canOperateIndustryVoucher &&
|
||||
!canCloseTasks &&
|
||||
!hasCloudActions ? (
|
||||
<Typography.Text type="secondary">当前账号没有可执行的任务操作</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -668,7 +670,9 @@ function TaskActionPanel({
|
||||
}
|
||||
|
||||
function isUsableClaimLinkStatus(value: unknown) {
|
||||
const normalized = String(value || '').trim().toLowerCase()
|
||||
const normalized = String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
return normalized === 'active' || normalized === 'external'
|
||||
}
|
||||
|
||||
@@ -684,12 +688,7 @@ function FulfillmentOverviewPanel({
|
||||
const executor = resolveTaskExecutorDisplay(detail.task.executorKey)
|
||||
const steps = buildFulfillmentOverviewSteps(detail, claimUrl, claimLinkInvalid)
|
||||
const claimIdentity = detail.claimIdentity
|
||||
const currentBlocker = resolveTaskBlocker(
|
||||
detail.task,
|
||||
claimUrl,
|
||||
claimLinkInvalid,
|
||||
claimIdentity,
|
||||
)
|
||||
const currentBlocker = resolveTaskBlocker(detail.task, claimUrl, claimLinkInvalid, claimIdentity)
|
||||
const uidMatchTag =
|
||||
claimIdentity?.uidMatched === true
|
||||
? { color: 'success' as const, text: 'UID 已匹配' }
|
||||
@@ -700,10 +699,7 @@ function FulfillmentOverviewPanel({
|
||||
: { color: 'warning' as const, text: '未填 UID' }
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="履约链路总览"
|
||||
extra={<Tag color={executor.color}>{executor.label}</Tag>}
|
||||
>
|
||||
<Card title="履约链路总览" extra={<Tag color={executor.color}>{executor.label}</Tag>}>
|
||||
<Descriptions
|
||||
size="small"
|
||||
column={{ xs: 1, sm: 2, md: 3 }}
|
||||
@@ -711,9 +707,7 @@ function FulfillmentOverviewPanel({
|
||||
style={{ marginBottom: 12 }}
|
||||
>
|
||||
<Descriptions.Item label="填写 UID">
|
||||
{claimIdentity?.expectedUid || (
|
||||
<Typography.Text type="warning">未提交</Typography.Text>
|
||||
)}
|
||||
{claimIdentity?.expectedUid || <Typography.Text type="warning">未提交</Typography.Text>}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="绑定角色 ID">
|
||||
{claimIdentity?.boundUid || detail.task.roleId || '-'}
|
||||
@@ -855,7 +849,10 @@ function resolveTaskBlocker(
|
||||
claimLinkInvalid: boolean,
|
||||
claimIdentity?: AdminTaskDetail['claimIdentity'],
|
||||
): { level: 'success' | 'info' | 'warning' | 'error'; title: string; detail: string } {
|
||||
if (['completed', 'redeemed', 'delivered'].includes(task.status) || task.deliveryStatus === 'delivered') {
|
||||
if (
|
||||
['completed', 'redeemed', 'delivered'].includes(task.status) ||
|
||||
task.deliveryStatus === 'delivered'
|
||||
) {
|
||||
return {
|
||||
level: 'success',
|
||||
title: '履约已完成',
|
||||
@@ -871,11 +868,7 @@ function resolveTaskBlocker(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
task.executorKey === 'kuaishou_ct_assisted' &&
|
||||
claimIdentity &&
|
||||
!claimIdentity.ready
|
||||
) {
|
||||
if (task.executorKey === 'kuaishou_ct_assisted' && claimIdentity && !claimIdentity.ready) {
|
||||
return {
|
||||
level: 'warning',
|
||||
title: '等待用户填写 UID',
|
||||
@@ -883,10 +876,7 @@ function resolveTaskBlocker(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
task.executorKey === 'kuaishou_ct_assisted' &&
|
||||
claimIdentity?.uidMatched === false
|
||||
) {
|
||||
if (task.executorKey === 'kuaishou_ct_assisted' && claimIdentity?.uidMatched === false) {
|
||||
return {
|
||||
level: 'error',
|
||||
title: 'UID 与绑定角色不一致',
|
||||
@@ -963,7 +953,11 @@ function resolveDeliveryStepSummary(detail: AdminTaskDetail) {
|
||||
}
|
||||
|
||||
if (detail.manualDispatch) {
|
||||
return detail.manualDispatch.resultMessage || detail.manualDispatch.deliveryReference || '人工履约已回写'
|
||||
return (
|
||||
detail.manualDispatch.resultMessage ||
|
||||
detail.manualDispatch.deliveryReference ||
|
||||
'人工履约已回写'
|
||||
)
|
||||
}
|
||||
|
||||
return detail.task.resultMessage || detail.task.lastError || '等待执行器推进'
|
||||
@@ -1119,9 +1113,7 @@ function KuaishouIndustryVoucherPanel({ voucher }: { voucher: KuaishouIndustryVo
|
||||
<Descriptions.Item label="发码时间">
|
||||
{formatAdminDateTime(voucher.sendCallbackSentAt)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="核销流水">
|
||||
{voucher.consumeSerialNum || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="核销流水">{voucher.consumeSerialNum || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="核销时间">
|
||||
{formatAdminDateTime(voucher.consumedAt)}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -144,11 +144,7 @@ export default function AdminTasksPage() {
|
||||
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<Tag
|
||||
className="task-list-blocker-tag"
|
||||
color={blocker.color}
|
||||
title={blocker.detail}
|
||||
>
|
||||
<Tag className="task-list-blocker-tag" color={blocker.color} title={blocker.detail}>
|
||||
{blocker.label}
|
||||
</Tag>
|
||||
{blocker.showDetail ? (
|
||||
@@ -326,7 +322,10 @@ export default function AdminTasksPage() {
|
||||
})
|
||||
}
|
||||
|
||||
function resolveTaskListAction(actionKey: TaskListAction, item: AdminTaskListItem): {
|
||||
function resolveTaskListAction(
|
||||
actionKey: TaskListAction,
|
||||
item: AdminTaskListItem,
|
||||
): {
|
||||
action: () => Promise<{ data: AdminTaskActionResponse }>
|
||||
confirmText: string
|
||||
successMessage: string
|
||||
@@ -570,9 +569,13 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
||||
}
|
||||
|
||||
if (
|
||||
['not_started', 'pending', 'pending_prepare', 'pending_binding', 'pending_binding_prepare'].includes(
|
||||
resourceStatus,
|
||||
)
|
||||
[
|
||||
'not_started',
|
||||
'pending',
|
||||
'pending_prepare',
|
||||
'pending_binding',
|
||||
'pending_binding_prepare',
|
||||
].includes(resourceStatus)
|
||||
) {
|
||||
return {
|
||||
label: '等资源',
|
||||
@@ -619,7 +622,9 @@ function resolveTaskListBlocker(item: AdminTaskListItem): TaskListBlocker {
|
||||
}
|
||||
|
||||
function normalizeTaskListStatus(value: unknown) {
|
||||
return String(value || '').trim().toLowerCase()
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function cleanParams(params: Record<string, unknown>) {
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
DownOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { DownOutlined, PlusOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
@@ -403,7 +398,11 @@ export default function AdminUsersPage() {
|
||||
|
||||
<Card
|
||||
title="用户列表"
|
||||
extra={<Typography.Text type="secondary">共 {data?.pagination.total || 0} 个账号</Typography.Text>}
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
共 {data?.pagination.total || 0} 个账号
|
||||
</Typography.Text>
|
||||
}
|
||||
>
|
||||
<Table<AdminUserListItem>
|
||||
rowKey="userId"
|
||||
|
||||
@@ -75,10 +75,7 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import CategoriesPanel from './panels/CategoriesPanel'
|
||||
import FinancePanel from './panels/FinancePanel'
|
||||
@@ -97,10 +94,7 @@ export default function AdminWorkerPlatformPage() {
|
||||
|
||||
return (
|
||||
<section className="page-stack">
|
||||
<PageHeader
|
||||
title="接单平台"
|
||||
description="管理打手、接单工单、等级权限和本地 Mock 工单。"
|
||||
/>
|
||||
<PageHeader title="接单平台" description="管理打手、接单工单、等级权限和本地 Mock 工单。" />
|
||||
<SummaryCards />
|
||||
<Tabs
|
||||
destroyOnHidden={false}
|
||||
|
||||
@@ -76,10 +76,7 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
export default function CategoriesPanel() {
|
||||
@@ -190,7 +187,10 @@ export default function CategoriesPanel() {
|
||||
|
||||
return (
|
||||
<section className="platform-panel-stack">
|
||||
<Card title={editingCategory ? `编辑分类:${editingCategory.name}` : '保存分类'} bordered={false}>
|
||||
<Card
|
||||
title={editingCategory ? `编辑分类:${editingCategory.name}` : '保存分类'}
|
||||
bordered={false}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="inline"
|
||||
@@ -204,11 +204,7 @@ export default function CategoriesPanel() {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
>
|
||||
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="排序" name="sortOrder">
|
||||
@@ -252,7 +248,8 @@ export default function CategoriesPanel() {
|
||||
cancelText="取消"
|
||||
>
|
||||
<Typography.Text>
|
||||
确定删除分类「{deletingCategory?.name}」?已引用该分类的工单和物品规则将无法删除,需先处理。
|
||||
确定删除分类「{deletingCategory?.name}
|
||||
」?已引用该分类的工单和物品规则将无法删除,需先处理。
|
||||
</Typography.Text>
|
||||
</Modal>
|
||||
</section>
|
||||
|
||||
@@ -76,12 +76,14 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { asRecord, formatMoney, formatSharingShareStatus, resolveSharingShareStatusColor } from './shared'
|
||||
import {
|
||||
asRecord,
|
||||
formatMoney,
|
||||
formatSharingShareStatus,
|
||||
resolveSharingShareStatusColor,
|
||||
} from './shared'
|
||||
type FinanceConfigFormValues = {
|
||||
depositUnfreezeDays?: number
|
||||
recharge?: {
|
||||
@@ -230,9 +232,7 @@ export default function FinancePanel() {
|
||||
<Typography.Text strong>
|
||||
{row.worker?.displayName || row.worker?.username || '-'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
{row.worker?.username || '-'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">{row.worker?.username || '-'}</Typography.Text>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -272,9 +272,7 @@ export default function FinancePanel() {
|
||||
<div className="cell-stack">
|
||||
<Typography.Text>{row.note || '-'}</Typography.Text>
|
||||
{row.reviewedNote ? (
|
||||
<Typography.Text type="secondary">
|
||||
审核备注:{row.reviewedNote}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">审核备注:{row.reviewedNote}</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
@@ -350,11 +348,7 @@ export default function FinancePanel() {
|
||||
initialValues={mapFinanceConfigToFormValues()}
|
||||
>
|
||||
<Card title="充值入口" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
label="开启充值申请"
|
||||
name={['recharge', 'enabled']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Form.Item label="开启充值申请" name={['recharge', 'enabled']} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="收款通道名称" name={['recharge', 'channelName']}>
|
||||
@@ -378,10 +372,7 @@ export default function FinancePanel() {
|
||||
</Card>
|
||||
|
||||
<Card title="管理员联系" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
label="管理员微信二维码"
|
||||
name={['adminContact', 'wechatQrCodeImageList']}
|
||||
>
|
||||
<Form.Item label="管理员微信二维码" name={['adminContact', 'wechatQrCodeImageList']}>
|
||||
<ImageUpload scene="worker-admin-contact" scope="admin" maxCount={1} />
|
||||
</Form.Item>
|
||||
</Card>
|
||||
@@ -400,11 +391,7 @@ export default function FinancePanel() {
|
||||
</Card>
|
||||
|
||||
<Card title="提现入口" size="small" style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
label="开启提现申请"
|
||||
name={['withdraw', 'enabled']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Form.Item label="开启提现申请" name={['withdraw', 'enabled']} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="提现说明" name={['withdraw', 'instructions']}>
|
||||
@@ -494,7 +481,9 @@ export default function FinancePanel() {
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={reviewState ? `${formatFinanceReviewAction(reviewState.action)}资金申请` : '处理资金申请'}
|
||||
title={
|
||||
reviewState ? `${formatFinanceReviewAction(reviewState.action)}资金申请` : '处理资金申请'
|
||||
}
|
||||
open={Boolean(reviewState)}
|
||||
destroyOnHidden
|
||||
confirmLoading={reviewing}
|
||||
@@ -553,9 +542,7 @@ export default function FinancePanel() {
|
||||
)
|
||||
}
|
||||
|
||||
function mapFinanceConfigToFormValues(
|
||||
config?: WorkerFinanceConfig,
|
||||
): FinanceConfigFormValues {
|
||||
function mapFinanceConfigToFormValues(config?: WorkerFinanceConfig): FinanceConfigFormValues {
|
||||
return {
|
||||
depositUnfreezeDays: Number(config?.depositUnfreezeDays ?? 3),
|
||||
recharge: {
|
||||
@@ -613,9 +600,7 @@ function renderFinanceRequestAccount(request: WorkerFinanceRequest) {
|
||||
<div className="cell-stack">
|
||||
<Typography.Text>{renderFinanceRequestAccountText(request)}</Typography.Text>
|
||||
{request.requestType === 'withdraw' && request.accountNo ? (
|
||||
<Typography.Text type="secondary">
|
||||
{maskFinanceAccount(request.accountNo)}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">{maskFinanceAccount(request.accountNo)}</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
@@ -629,11 +614,7 @@ function renderFinanceRequestAccountText(request: WorkerFinanceRequest) {
|
||||
}
|
||||
|
||||
const rechargeSnapshot = getRechargeSnapshot(request)
|
||||
if (
|
||||
rechargeSnapshot.channelName ||
|
||||
rechargeSnapshot.accountName ||
|
||||
rechargeSnapshot.accountNo
|
||||
) {
|
||||
if (rechargeSnapshot.channelName || rechargeSnapshot.accountName || rechargeSnapshot.accountNo) {
|
||||
return [
|
||||
rechargeSnapshot.channelName,
|
||||
rechargeSnapshot.accountName,
|
||||
|
||||
@@ -76,10 +76,7 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { formatMoney } from './shared'
|
||||
|
||||
@@ -179,8 +176,7 @@ export default function LevelsPanel() {
|
||||
{
|
||||
title: '可见延迟',
|
||||
width: 110,
|
||||
render: (_, row) =>
|
||||
row.visibleDelaySeconds > 0 ? `${row.visibleDelaySeconds} 秒` : '立即',
|
||||
render: (_, row) => (row.visibleDelaySeconds > 0 ? `${row.visibleDelaySeconds} 秒` : '立即'),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
@@ -212,10 +208,7 @@ export default function LevelsPanel() {
|
||||
|
||||
return (
|
||||
<section className="platform-panel-stack">
|
||||
<Card
|
||||
title={editingLevel ? `编辑等级:${editingLevel.name}` : '保存等级'}
|
||||
bordered={false}
|
||||
>
|
||||
<Card title={editingLevel ? `编辑等级:${editingLevel.name}` : '保存等级'} bordered={false}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="inline"
|
||||
@@ -236,11 +229,7 @@ export default function LevelsPanel() {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
>
|
||||
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="免押额度" name="depositFreeAmount">
|
||||
|
||||
@@ -66,7 +66,11 @@ export default function ProductRulesPanel() {
|
||||
rule.provider,
|
||||
rule.shopId,
|
||||
rule.categoryName,
|
||||
].some((value) => String(value || '').toLowerCase().includes(keywordText))
|
||||
].some((value) =>
|
||||
String(value || '')
|
||||
.toLowerCase()
|
||||
.includes(keywordText),
|
||||
)
|
||||
})
|
||||
|
||||
function resetRuleForm() {
|
||||
@@ -212,9 +216,7 @@ export default function ProductRulesPanel() {
|
||||
}) {
|
||||
const values = form.getFieldsValue()
|
||||
const unitReward = Number(next.sharingUnitReward ?? values.sharingUnitReward ?? 0)
|
||||
const totalQuantity = Number(
|
||||
next.sharingTotalQuantity ?? values.sharingTotalQuantity ?? 1,
|
||||
)
|
||||
const totalQuantity = Number(next.sharingTotalQuantity ?? values.sharingTotalQuantity ?? 1)
|
||||
const totalAmount = Number(next.sharingTotalAmount ?? values.sharingTotalAmount ?? 0)
|
||||
if (next.sharingTotalAmount !== undefined) {
|
||||
const quantity = unitReward > 0 ? Math.max(1, Math.round(totalAmount / unitReward)) : 1
|
||||
@@ -260,9 +262,7 @@ export default function ProductRulesPanel() {
|
||||
const shopId = String(row.shopId || '')
|
||||
return (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text style={{ fontSize: 12 }}>
|
||||
{row.platform || 'kuaishou'}
|
||||
</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 12 }}>{row.platform || 'kuaishou'}</Typography.Text>
|
||||
{shopId ? (
|
||||
<Tag color="geekblue" style={{ margin: 0, fontSize: 11, lineHeight: '18px' }}>
|
||||
{shopId}
|
||||
@@ -290,7 +290,9 @@ export default function ProductRulesPanel() {
|
||||
width: '11%',
|
||||
render: (value) =>
|
||||
value ? (
|
||||
<Tag color="cyan" style={{ margin: 0 }}>{value}</Tag>
|
||||
<Tag color="cyan" style={{ margin: 0 }}>
|
||||
{value}
|
||||
</Tag>
|
||||
) : (
|
||||
<Typography.Text type="secondary">-</Typography.Text>
|
||||
),
|
||||
@@ -413,11 +415,7 @@ export default function ProductRulesPanel() {
|
||||
取消编辑
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
form="product-rule-form"
|
||||
>
|
||||
<Button type="primary" htmlType="submit" form="product-rule-form">
|
||||
{editingRule ? '保存修改' : '创建规则'}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -455,10 +453,7 @@ export default function ProductRulesPanel() {
|
||||
{
|
||||
validator: (_, value) => {
|
||||
const key = String(value || '').trim()
|
||||
if (
|
||||
!editingRule &&
|
||||
allRules.some((rule) => rule.ruleKey === key)
|
||||
) {
|
||||
if (!editingRule && allRules.some((rule) => rule.ruleKey === key)) {
|
||||
return Promise.reject(new Error('标识已存在'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
@@ -677,11 +672,7 @@ export default function ProductRulesPanel() {
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={resetRuleForm}
|
||||
>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={resetRuleForm}>
|
||||
新建规则
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
@@ -91,10 +91,7 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import {
|
||||
@@ -187,8 +184,7 @@ export default function WorkOrdersPanel() {
|
||||
totalAmount?: number
|
||||
}>()
|
||||
// 仅单独筛选「待验收」时提供批量验收入口(多选状态下不启用勾选,避免误操作)
|
||||
const isPendingAcceptanceOnly =
|
||||
statuses.length === 1 && statuses[0] === 'pending_acceptance'
|
||||
const isPendingAcceptanceOnly = statuses.length === 1 && statuses[0] === 'pending_acceptance'
|
||||
|
||||
const ordersQuery = useQuery({
|
||||
queryKey: [
|
||||
@@ -436,8 +432,7 @@ export default function WorkOrdersPanel() {
|
||||
async function submitProblem(values: { note?: string }) {
|
||||
if (!problemOrder) return
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
markAdminWorkOrderProblem(problemOrder.workOrderId, values.note || ''),
|
||||
() => markAdminWorkOrderProblem(problemOrder.workOrderId, values.note || ''),
|
||||
'已标记问题单',
|
||||
)
|
||||
if (!succeeded) return
|
||||
@@ -473,9 +468,7 @@ export default function WorkOrdersPanel() {
|
||||
action,
|
||||
reason: String(values.reason || '').trim(),
|
||||
}),
|
||||
action === 'return_to_hall'
|
||||
? '订单已退回大厅,打手额度已恢复'
|
||||
: '订单已取消,打手额度已恢复',
|
||||
action === 'return_to_hall' ? '订单已退回大厅,打手额度已恢复' : '订单已取消,打手额度已恢复',
|
||||
)
|
||||
if (!succeeded) return
|
||||
setCancelOrder(null)
|
||||
@@ -535,8 +528,7 @@ export default function WorkOrdersPanel() {
|
||||
platformOrderId: row.platformOrderId || '',
|
||||
categoryId: row.categoryId ?? undefined,
|
||||
rewardAmount: Math.round(Number(row.rewardAmount || 0)) / 100,
|
||||
requiredDepositAmount:
|
||||
Math.round(Number(row.requiredDepositAmount || 0)) / 100,
|
||||
requiredDepositAmount: Math.round(Number(row.requiredDepositAmount || 0)) / 100,
|
||||
fieldsText: formatRequirementFieldsText(getRequirementFields(row)),
|
||||
timeoutMinutes: Number(row.timeoutMinutes || 0),
|
||||
timeoutPolicy: row.timeoutPolicy || 'reopen',
|
||||
@@ -586,9 +578,7 @@ export default function WorkOrdersPanel() {
|
||||
categoryId: values.categoryId || null,
|
||||
rewardAmount: Number(values.rewardAmount || 0),
|
||||
requiredDepositAmount: Number(values.requiredDepositAmount || 0),
|
||||
...(canEditRequirementFields
|
||||
? { fieldsText: String(values.fieldsText || '') }
|
||||
: {}),
|
||||
...(canEditRequirementFields ? { fieldsText: String(values.fieldsText || '') } : {}),
|
||||
timeoutMinutes: Math.max(0, Number(values.timeoutMinutes || 0)),
|
||||
timeoutPolicy: values.timeoutPolicy || 'reopen',
|
||||
}),
|
||||
@@ -602,7 +592,9 @@ export default function WorkOrdersPanel() {
|
||||
content: (
|
||||
<span>
|
||||
该订单用户付款 {formatMoney(paymentFen)},发单金额{' '}
|
||||
<Typography.Text strong type="danger">{formatMoney(rewardFen)}</Typography.Text>
|
||||
<Typography.Text strong type="danger">
|
||||
{formatMoney(rewardFen)}
|
||||
</Typography.Text>
|
||||
,本单平台将亏损。确认保存?
|
||||
</span>
|
||||
),
|
||||
@@ -631,11 +623,7 @@ export default function WorkOrdersPanel() {
|
||||
okText: '确认删除',
|
||||
okButtonProps: { danger: true },
|
||||
cancelText: '取消',
|
||||
onOk: () =>
|
||||
runAction(
|
||||
() => deleteAdminWorkOrder(row.workOrderId),
|
||||
'工单已删除',
|
||||
),
|
||||
onOk: () => runAction(() => deleteAdminWorkOrder(row.workOrderId), '工单已删除'),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -657,9 +645,7 @@ export default function WorkOrdersPanel() {
|
||||
<Space size={[6, 6]} wrap>
|
||||
{row.categoryName ? <Tag>{row.categoryName}</Tag> : null}
|
||||
{row.orderId ? (
|
||||
<Typography.Text type="secondary">
|
||||
源订单 #{row.orderId}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">源订单 #{row.orderId}</Typography.Text>
|
||||
) : null}
|
||||
</Space>
|
||||
{shopName || provider ? (
|
||||
@@ -669,9 +655,7 @@ export default function WorkOrdersPanel() {
|
||||
{shopName ? `店铺:${shopName}` : ''}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
<Typography.Text type="secondary">
|
||||
{getDisplayOrderNo(row)}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">{getDisplayOrderNo(row)}</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -748,11 +732,11 @@ export default function WorkOrdersPanel() {
|
||||
width: 150,
|
||||
render: (_, row) => (
|
||||
<Space wrap size={4}>
|
||||
<Tag color={resolveStatusColor(row.status)}>
|
||||
{formatStatus(row.status)}
|
||||
</Tag>
|
||||
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
|
||||
{row.status === 'open' && row.pinnedAt ? (
|
||||
<Tag color="gold" icon={<PushpinOutlined />}>置顶</Tag>
|
||||
<Tag color="gold" icon={<PushpinOutlined />}>
|
||||
置顶
|
||||
</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
@@ -760,8 +744,7 @@ export default function WorkOrdersPanel() {
|
||||
{
|
||||
title: '打手',
|
||||
width: 140,
|
||||
render: (_, row) =>
|
||||
row.worker?.displayName || row.worker?.username || '-',
|
||||
render: (_, row) => row.worker?.displayName || row.worker?.username || '-',
|
||||
},
|
||||
{
|
||||
title: '资料',
|
||||
@@ -787,27 +770,17 @@ export default function WorkOrdersPanel() {
|
||||
<Space wrap>
|
||||
<Button onClick={() => openDetailDrawer(row)}>详情</Button>
|
||||
{['pending_material', 'unassigned', 'open'].includes(row.status) ? (
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => openEditModal(row)}
|
||||
>
|
||||
<Button icon={<EditOutlined />} onClick={() => openEditModal(row)}>
|
||||
编辑
|
||||
</Button>
|
||||
) : null}
|
||||
{['pending_material', 'unassigned', 'cancelled'].includes(row.status) ? (
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => requestDeleteWorkOrder(row)}
|
||||
>
|
||||
<Button danger icon={<DeleteOutlined />} onClick={() => requestDeleteWorkOrder(row)}>
|
||||
删除
|
||||
</Button>
|
||||
) : null}
|
||||
{['pending_material', 'unassigned', 'open'].includes(row.status) ? (
|
||||
<Button
|
||||
icon={<TeamOutlined />}
|
||||
onClick={() => openSharingConfigModal(row)}
|
||||
>
|
||||
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
||||
拼单配置
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -817,19 +790,12 @@ export default function WorkOrdersPanel() {
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'in_progress' && row.worker ? (
|
||||
<Button
|
||||
danger
|
||||
icon={<UndoOutlined />}
|
||||
onClick={() => openCancelModal(row)}
|
||||
>
|
||||
<Button danger icon={<UndoOutlined />} onClick={() => openCancelModal(row)}>
|
||||
撤单
|
||||
</Button>
|
||||
) : null}
|
||||
{['pending_material', 'in_progress'].includes(row.status) ? (
|
||||
<Button
|
||||
icon={<FormOutlined />}
|
||||
onClick={() => openMaterialModal(row)}
|
||||
>
|
||||
<Button icon={<FormOutlined />} onClick={() => openMaterialModal(row)}>
|
||||
{row.status === 'in_progress' ? '更新资料' : '补资料'}
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -838,9 +804,7 @@ export default function WorkOrdersPanel() {
|
||||
icon={<SendOutlined />}
|
||||
disabled={Number(row.rewardAmount || 0) <= 0}
|
||||
title={
|
||||
Number(row.rewardAmount || 0) <= 0
|
||||
? '接单金额为 0,不能发布到大厅'
|
||||
: undefined
|
||||
Number(row.rewardAmount || 0) <= 0 ? '接单金额为 0,不能发布到大厅' : undefined
|
||||
}
|
||||
onClick={() => confirmPublishWorkOrder(row)}
|
||||
>
|
||||
@@ -871,10 +835,7 @@ export default function WorkOrdersPanel() {
|
||||
okText: '确认下架',
|
||||
cancelText: '取消',
|
||||
onOk: () =>
|
||||
runAction(
|
||||
() => unpublishAdminWorkOrder(row.workOrderId),
|
||||
'订单已撤回到未分配',
|
||||
),
|
||||
runAction(() => unpublishAdminWorkOrder(row.workOrderId), '订单已撤回到未分配'),
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -885,31 +846,18 @@ export default function WorkOrdersPanel() {
|
||||
<Button
|
||||
icon={<CheckOutlined />}
|
||||
type="primary"
|
||||
onClick={() =>
|
||||
runAction(
|
||||
() => acceptAdminWorkOrder(row.workOrderId),
|
||||
'已验收通过',
|
||||
)
|
||||
}
|
||||
onClick={() => runAction(() => acceptAdminWorkOrder(row.workOrderId), '已验收通过')}
|
||||
>
|
||||
验收
|
||||
</Button>
|
||||
) : null}
|
||||
{row.status === 'accepted' && Number(row.pendingUnfreezeAmount || 0) > 0 ? (
|
||||
<Button
|
||||
icon={<WarningOutlined />}
|
||||
danger
|
||||
onClick={() => openDeductModal(row)}
|
||||
>
|
||||
<Button icon={<WarningOutlined />} danger onClick={() => openDeductModal(row)}>
|
||||
扣押金
|
||||
</Button>
|
||||
) : null}
|
||||
{['in_progress', 'pending_acceptance'].includes(row.status) ? (
|
||||
<Button
|
||||
icon={<WarningOutlined />}
|
||||
danger
|
||||
onClick={() => setProblemOrder(row)}
|
||||
>
|
||||
<Button icon={<WarningOutlined />} danger onClick={() => setProblemOrder(row)}>
|
||||
问题单
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -1078,9 +1026,7 @@ export default function WorkOrdersPanel() {
|
||||
<Descriptions.Item label="订单子项 ID">
|
||||
{detailOrder.orderItemId || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="商品">
|
||||
{detailOrder.productName || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="商品">{detailOrder.productName || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="分类">
|
||||
{detailOrder.categoryName || '-'}
|
||||
</Descriptions.Item>
|
||||
@@ -1090,9 +1036,7 @@ export default function WorkOrdersPanel() {
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="打手">
|
||||
{detailOrder.worker?.displayName ||
|
||||
detailOrder.worker?.username ||
|
||||
'-'}
|
||||
{detailOrder.worker?.displayName || detailOrder.worker?.username || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="接单金额">
|
||||
{formatMoney(detailOrder.rewardAmount)}
|
||||
@@ -1111,10 +1055,7 @@ export default function WorkOrdersPanel() {
|
||||
<Descriptions.Item label="超时截止">
|
||||
{detailOrder.deadlineAt ? (
|
||||
<Space wrap size={8}>
|
||||
<DeadlineCountdown
|
||||
deadlineAt={detailOrder.deadlineAt}
|
||||
showExpired
|
||||
/>
|
||||
<DeadlineCountdown deadlineAt={detailOrder.deadlineAt} showExpired />
|
||||
<Typography.Text type="secondary">
|
||||
{formatAdminDateTime(detailOrder.deadlineAt)}
|
||||
</Typography.Text>
|
||||
@@ -1150,10 +1091,7 @@ export default function WorkOrdersPanel() {
|
||||
</Card>
|
||||
|
||||
<Card title="资料信息" size="small">
|
||||
{renderDetailFieldValues(
|
||||
getMaterialDetailItems(detailOrder),
|
||||
'当前还没有填写资料',
|
||||
)}
|
||||
{renderDetailFieldValues(getMaterialDetailItems(detailOrder), '当前还没有填写资料')}
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<Typography.Text type="secondary">买家主页截图</Typography.Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
@@ -1186,9 +1124,7 @@ export default function WorkOrdersPanel() {
|
||||
size={72}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text type="secondary">
|
||||
暂无验收图
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">暂无验收图</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
@@ -1282,12 +1218,7 @@ export default function WorkOrdersPanel() {
|
||||
name="timeoutMinutes"
|
||||
tooltip="打手抢单后开始计时,超时由系统自动按策略处置;0 表示不限时"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={5}
|
||||
addonAfter="分钟"
|
||||
style={{ width: 160 }}
|
||||
/>
|
||||
<InputNumber min={0} step={5} addonAfter="分钟" style={{ width: 160 }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="超时策略"
|
||||
@@ -1454,10 +1385,7 @@ export default function WorkOrdersPanel() {
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (
|
||||
getFieldValue('action') !== 'cancel_order' ||
|
||||
String(value || '').trim()
|
||||
) {
|
||||
if (getFieldValue('action') !== 'cancel_order' || String(value || '').trim()) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.reject(new Error('取消订单时请填写退款或撤单原因'))
|
||||
@@ -1465,10 +1393,7 @@ export default function WorkOrdersPanel() {
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
placeholder="取消订单时请填写退款原因;退回大厅可选填"
|
||||
/>
|
||||
<Input.TextArea rows={3} placeholder="取消订单时请填写退款原因;退回大厅可选填" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
@@ -1658,9 +1583,7 @@ export default function WorkOrdersPanel() {
|
||||
min={0.01}
|
||||
step={1}
|
||||
addonAfter="元"
|
||||
onChange={(value) =>
|
||||
syncSharingForm({ unitReward: Number(value) || 0 })
|
||||
}
|
||||
onChange={(value) => syncSharingForm({ unitReward: Number(value) || 0 })}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
@@ -1673,9 +1596,7 @@ export default function WorkOrdersPanel() {
|
||||
min={0.01}
|
||||
step={1}
|
||||
addonAfter="元"
|
||||
onChange={(value) =>
|
||||
syncSharingForm({ totalAmount: Number(value) || 0 })
|
||||
}
|
||||
onChange={(value) => syncSharingForm({ totalAmount: Number(value) || 0 })}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
@@ -1717,9 +1638,7 @@ export default function WorkOrdersPanel() {
|
||||
{
|
||||
title: '提交时间',
|
||||
render: (_, share) =>
|
||||
share.submittedAt
|
||||
? formatAdminDateTime(share.submittedAt)
|
||||
: '-',
|
||||
share.submittedAt ? formatAdminDateTime(share.submittedAt) : '-',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1910,7 +1829,9 @@ function AmountEditableCell({
|
||||
content: (
|
||||
<span>
|
||||
该订单用户付款 {formatMoney(paymentFen)},发单金额{' '}
|
||||
<Typography.Text strong type="danger">{formatMoney(nextFen)}</Typography.Text>
|
||||
<Typography.Text strong type="danger">
|
||||
{formatMoney(nextFen)}
|
||||
</Typography.Text>
|
||||
,本单平台将亏损。确认保存?
|
||||
</span>
|
||||
),
|
||||
@@ -2002,9 +1923,7 @@ function formatRequirementFieldsText(fields: CollectField[]): string {
|
||||
.map((field) => {
|
||||
const labelPart = `${field.key}:${field.label}`
|
||||
const options = (field.options || []).filter(Boolean)
|
||||
return options.length > 0
|
||||
? `${labelPart}#${options.join(',')}`
|
||||
: labelPart
|
||||
return options.length > 0 ? `${labelPart}#${options.join(',')}` : labelPart
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
@@ -2069,12 +1988,7 @@ function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise<void> }) {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item className="worker-order-mock-action">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
htmlType="submit"
|
||||
loading={loading}
|
||||
>
|
||||
<Button type="primary" icon={<PlusOutlined />} htmlType="submit" loading={loading}>
|
||||
生成
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
||||
@@ -76,10 +76,7 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
buildAdminTablePagination,
|
||||
} from '@/utils/admin-pagination'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import { formatMoney, formatWorkerStatus } from './shared'
|
||||
|
||||
@@ -189,9 +186,7 @@ export default function WorkersPanel() {
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Space size={6} wrap>
|
||||
<Typography.Text strong>
|
||||
{row.displayName || row.username}
|
||||
</Typography.Text>
|
||||
<Typography.Text strong>{row.displayName || row.username}</Typography.Text>
|
||||
<Tag color={row.workerType === 'internal' ? 'blue' : 'default'}>
|
||||
{row.workerType === 'internal' ? '内部' : '外部'}
|
||||
</Tag>
|
||||
@@ -368,17 +363,8 @@ export default function WorkersPanel() {
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form form={creditForm} layout="vertical" onFinish={submitCredit}>
|
||||
<Form.Item
|
||||
label="金额"
|
||||
name="amount"
|
||||
rules={[{ required: true, message: '请输入金额' }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={0.01}
|
||||
step={10}
|
||||
addonAfter="元"
|
||||
className="full-width"
|
||||
/>
|
||||
<Form.Item label="金额" name="amount" rules={[{ required: true, message: '请输入金额' }]}>
|
||||
<InputNumber min={0.01} step={10} addonAfter="元" className="full-width" />
|
||||
</Form.Item>
|
||||
<Form.Item label="备注" name="note">
|
||||
<Input />
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Descriptions, Space, Tag, Typography } from 'antd'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import {
|
||||
DeadlineCountdown,
|
||||
formatTimeoutPolicyLabel,
|
||||
} from '@/components/DeadlineCountdown'
|
||||
import { DeadlineCountdown, formatTimeoutPolicyLabel } from '@/components/DeadlineCountdown'
|
||||
import type { CollectField, UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
@@ -19,9 +16,7 @@ type MappedRequirementField = {
|
||||
|
||||
export function getRequirementFields(order: WorkOrder | null): CollectField[] {
|
||||
if (!order) return []
|
||||
const rawFields = Array.isArray(order.requirement?.fields)
|
||||
? order.requirement.fields
|
||||
: []
|
||||
const rawFields = Array.isArray(order.requirement?.fields) ? order.requirement.fields : []
|
||||
return rawFields
|
||||
.map((item) => {
|
||||
const source = asRecord(item)
|
||||
@@ -83,10 +78,7 @@ export function getMaterialDetailItems(order: WorkOrder): DetailFieldItem[] {
|
||||
return items
|
||||
}
|
||||
|
||||
export function renderDetailFieldValues(
|
||||
items: DetailFieldItem[],
|
||||
emptyText: string,
|
||||
) {
|
||||
export function renderDetailFieldValues(items: DetailFieldItem[], emptyText: string) {
|
||||
if (items.length === 0) {
|
||||
return <Typography.Text type="secondary">{emptyText}</Typography.Text>
|
||||
}
|
||||
@@ -139,9 +131,7 @@ export function renderMaterialSummary(order: WorkOrder) {
|
||||
getRequirementFields(order).map((field) => [field.key, field.label]),
|
||||
)
|
||||
const fields = getMaterialFields(order)
|
||||
const entries = Object.entries(fields).filter(([, value]) =>
|
||||
String(value || '').trim(),
|
||||
)
|
||||
const entries = Object.entries(fields).filter(([, value]) => String(value || '').trim())
|
||||
const screenshots = getMaterialScreenshots(order)
|
||||
if (entries.length === 0 && screenshots.length === 0) {
|
||||
return <Typography.Text type="secondary">-</Typography.Text>
|
||||
@@ -154,9 +144,7 @@ export function renderMaterialSummary(order: WorkOrder) {
|
||||
{fieldLabelMap.get(key) || key}: {value}
|
||||
</Typography.Text>
|
||||
))}
|
||||
{screenshots.length > 0 ? (
|
||||
<ImagePreviewList files={screenshots} size={36} />
|
||||
) : null}
|
||||
{screenshots.length > 0 ? <ImagePreviewList files={screenshots} size={36} /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -167,17 +155,13 @@ export function getAcceptanceFiles(row: WorkOrder): UploadedFile[] {
|
||||
|
||||
export function getAcceptanceImageUrls(row: WorkOrder): string[] {
|
||||
return Array.isArray(row.acceptance?.imageUrls)
|
||||
? row.acceptance.imageUrls
|
||||
.map((item) => String(item || '').trim())
|
||||
.filter(Boolean)
|
||||
? row.acceptance.imageUrls.map((item) => String(item || '').trim()).filter(Boolean)
|
||||
: []
|
||||
}
|
||||
|
||||
export function getAcceptanceSubmittedAt(order: WorkOrder): string | null {
|
||||
const acceptanceSubmittedAt =
|
||||
typeof order.acceptance?.submittedAt === 'string'
|
||||
? order.acceptance.submittedAt
|
||||
: null
|
||||
typeof order.acceptance?.submittedAt === 'string' ? order.acceptance.submittedAt : null
|
||||
return order.submittedAt || acceptanceSubmittedAt || null
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,9 @@ function FulfillmentRoutingPanel() {
|
||||
const [executors, setExecutors] = useState<AdminFulfillmentRoutingConfig['executors']>({})
|
||||
const [rules, setRules] = useState<EditableRoutingRule[]>([])
|
||||
const [previewInput, setPreviewInput] = useState('')
|
||||
const [previewResult, setPreviewResult] = useState<AdminFulfillmentRoutingPreviewResult | null>(null)
|
||||
const [previewResult, setPreviewResult] = useState<AdminFulfillmentRoutingPreviewResult | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
const enabledRuleCount = rules.filter((rule) => rule.enabled !== false).length
|
||||
const selectedExecutorKey = previewResult?.fulfillmentRoute?.selectedExecutorKey || ''
|
||||
@@ -276,7 +278,11 @@ function FulfillmentRoutingPanel() {
|
||||
|
||||
<div className="metric-grid four">
|
||||
<MetricCard label="路由状态" value={enabled ? '启用' : '停用'} />
|
||||
<MetricCard label="商品规则" value={String(rules.length)} detail={`启用 ${enabledRuleCount} 条`} />
|
||||
<MetricCard
|
||||
label="商品规则"
|
||||
value={String(rules.length)}
|
||||
detail={`启用 ${enabledRuleCount} 条`}
|
||||
/>
|
||||
<MetricCard
|
||||
label="全局优先级"
|
||||
value={`${priority.length} 个通道`}
|
||||
@@ -298,7 +304,12 @@ function FulfillmentRoutingPanel() {
|
||||
<div className="platform-routing-grid">
|
||||
<div className="field-switch">
|
||||
<span>履约路由</span>
|
||||
<Switch checked={enabled} checkedChildren="启用" unCheckedChildren="停用" onChange={setEnabled} />
|
||||
<Switch
|
||||
checked={enabled}
|
||||
checkedChildren="启用"
|
||||
unCheckedChildren="停用"
|
||||
onChange={setEnabled}
|
||||
/>
|
||||
</div>
|
||||
{ROUTING_EXECUTORS.map((executor) => (
|
||||
<div key={executor.key} className="field-switch">
|
||||
@@ -610,14 +621,23 @@ function KuaishouFeifeiFulfillmentPanel() {
|
||||
{errorMessage ? <Alert type="error" showIcon message={errorMessage} /> : null}
|
||||
|
||||
<div className="metric-grid three">
|
||||
<MetricCard label="名字映射" value={String(mappingCount)} detail={`启用 ${enabledRuleCount} 条`} />
|
||||
<MetricCard
|
||||
label="名字映射"
|
||||
value={String(mappingCount)}
|
||||
detail={`启用 ${enabledRuleCount} 条`}
|
||||
/>
|
||||
<MetricCard label="匹配方式" value="同名" detail="使用 91 商品名规范化匹配" />
|
||||
<Card>
|
||||
<Space wrap>
|
||||
<Button icon={<ReloadOutlined />} loading={loading} onClick={loadConfig}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button type="primary" icon={<ReloadOutlined />} loading={syncing} onClick={syncProducts}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
loading={syncing}
|
||||
onClick={syncProducts}
|
||||
>
|
||||
同步商品映射
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -626,7 +646,11 @@ function KuaishouFeifeiFulfillmentPanel() {
|
||||
|
||||
<Card
|
||||
title="商品名字映射"
|
||||
extra={<Typography.Text type="secondary">{lastSyncText || 'feifei 商品名会按规范化名字匹配 91 商品名。'}</Typography.Text>}
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
{lastSyncText || 'feifei 商品名会按规范化名字匹配 91 商品名。'}
|
||||
</Typography.Text>
|
||||
}
|
||||
>
|
||||
<Space.Compact className="full-width">
|
||||
<Input
|
||||
@@ -714,7 +738,10 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
sourceCount: sources.length,
|
||||
readySourceCount: readySources.length,
|
||||
skuCount: skuItems.length,
|
||||
inventoryTotal: skuItems.reduce((sum, item) => sum + Math.max(0, Number(item.inventory || 0)), 0),
|
||||
inventoryTotal: skuItems.reduce(
|
||||
(sum, item) => sum + Math.max(0, Number(item.inventory || 0)),
|
||||
0,
|
||||
),
|
||||
overrideRuleCount: overrideRules.length,
|
||||
}
|
||||
|
||||
@@ -739,7 +766,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
fetchAdminCloudtentaclesSourceConfig(),
|
||||
fetchAdminCloudtentaclesOverrideRules(),
|
||||
])
|
||||
const nextSources = Array.isArray(sourceResponse.data.sources) ? sourceResponse.data.sources : []
|
||||
const nextSources = Array.isArray(sourceResponse.data.sources)
|
||||
? sourceResponse.data.sources
|
||||
: []
|
||||
const nextSessions = sourceResponse.data.sessions || {}
|
||||
const nextRows = nextSources.map((source) => {
|
||||
const session = nextSessions[source.key] || null
|
||||
@@ -839,7 +868,11 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
function updateDeliveryItem(ruleIndex: number, itemIndex: number, patch: Partial<AdminCloudtentaclesOverrideDeliveryItem>) {
|
||||
function updateDeliveryItem(
|
||||
ruleIndex: number,
|
||||
itemIndex: number,
|
||||
patch: Partial<AdminCloudtentaclesOverrideDeliveryItem>,
|
||||
) {
|
||||
setOverrideRules((current) =>
|
||||
current.map((rule, currentRuleIndex) => {
|
||||
if (currentRuleIndex !== ruleIndex) {
|
||||
@@ -877,7 +910,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
return rule
|
||||
}
|
||||
|
||||
const nextItems = rule.deliveryItems.filter((_, currentItemIndex) => currentItemIndex !== itemIndex)
|
||||
const nextItems = rule.deliveryItems.filter(
|
||||
(_, currentItemIndex) => currentItemIndex !== itemIndex,
|
||||
)
|
||||
return {
|
||||
...rule,
|
||||
deliveryItems: nextItems.length ? nextItems : [createDeliveryItem(skuItems)],
|
||||
@@ -903,7 +938,10 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
{errorMessage ? <Alert type="error" showIcon message={errorMessage} /> : null}
|
||||
|
||||
<div className="metric-grid five">
|
||||
<MetricCard label="履约账号" value={`${metrics.readySourceCount} / ${metrics.sourceCount}`} />
|
||||
<MetricCard
|
||||
label="履约账号"
|
||||
value={`${metrics.readySourceCount} / ${metrics.sourceCount}`}
|
||||
/>
|
||||
<MetricCard label="可发商品" value={String(metrics.skuCount)} />
|
||||
<MetricCard label="总库存" value={String(metrics.inventoryTotal)} />
|
||||
<MetricCard label="覆盖规则" value={String(metrics.overrideRuleCount)} />
|
||||
@@ -935,7 +973,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
<strong>{formatSourceLabel(source)}</strong>
|
||||
<small>{source.username || source.key}</small>
|
||||
</span>
|
||||
<Tag color={source.ready ? 'green' : source.enabled === false ? 'default' : 'orange'}>
|
||||
<Tag
|
||||
color={source.ready ? 'green' : source.enabled === false ? 'default' : 'orange'}
|
||||
>
|
||||
{source.ready ? '可用' : source.enabled === false ? '已停用' : '未登录'}
|
||||
</Tag>
|
||||
</button>
|
||||
@@ -964,7 +1004,12 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
}
|
||||
>
|
||||
{skuErrorMessage ? (
|
||||
<Alert type="error" showIcon message={skuErrorMessage} className="platform-section-gap" />
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message={skuErrorMessage}
|
||||
className="platform-section-gap"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Space className="full-width" direction="vertical">
|
||||
@@ -980,7 +1025,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
{matchedSku ? (
|
||||
<Tag color="green">命中 #{matchedSku.id} {matchedSku.name}</Tag>
|
||||
<Tag color="green">
|
||||
命中 #{matchedSku.id} {matchedSku.name}
|
||||
</Tag>
|
||||
) : matchInput.trim() ? (
|
||||
<Tag color="orange">未命中</Tag>
|
||||
) : null}
|
||||
@@ -1012,7 +1059,12 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
}
|
||||
>
|
||||
{overrideErrorMessage ? (
|
||||
<Alert type="error" showIcon message={overrideErrorMessage} className="platform-section-gap" />
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message={overrideErrorMessage}
|
||||
className="platform-section-gap"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{overrideRules.length === 0 ? (
|
||||
@@ -1048,7 +1100,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
<Input
|
||||
value={rule.productName}
|
||||
placeholder="91 商品名 / productNo"
|
||||
onChange={(event) => updateRule(ruleIndex, { productName: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateRule(ruleIndex, { productName: event.target.value })
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
@@ -1070,7 +1124,11 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Space direction="vertical" className="full-width platform-section-gap" size={8}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
className="full-width platform-section-gap"
|
||||
size={8}
|
||||
>
|
||||
{rule.deliveryItems.map((item, itemIndex) => (
|
||||
<div key={`${rule.id}-${itemIndex}`} className="platform-delivery-row">
|
||||
<Select
|
||||
@@ -1082,7 +1140,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
label: formatSkuOption(sku),
|
||||
value: sku.id,
|
||||
}))}
|
||||
onChange={(skuId) => handleDeliverySkuChange(ruleIndex, itemIndex, Number(skuId || 0))}
|
||||
onChange={(skuId) =>
|
||||
handleDeliverySkuChange(ruleIndex, itemIndex, Number(skuId || 0))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
value={item.cloudSkuName}
|
||||
@@ -1103,7 +1163,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button onClick={() => removeDeliveryItem(ruleIndex, itemIndex)}>删除</Button>
|
||||
<Button onClick={() => removeDeliveryItem(ruleIndex, itemIndex)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button onClick={() => addDeliveryItem(ruleIndex)}>增加发货商品</Button>
|
||||
@@ -1133,7 +1195,9 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
minWidth: 260,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<Typography.Text strong ellipsis>{row.name || '-'}</Typography.Text>
|
||||
<Typography.Text strong ellipsis>
|
||||
{row.name || '-'}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" ellipsis>
|
||||
#{row.id} · {row.description || row.name || '-'}
|
||||
</Typography.Text>
|
||||
@@ -1147,7 +1211,12 @@ function KuaishouCloudFulfillmentPanel() {
|
||||
render: (value) => <code>{String(value || '-')}</code>,
|
||||
},
|
||||
{ title: '价格', dataIndex: 'price', width: 120, render: formatCloudPrice },
|
||||
{ title: '库存', dataIndex: 'inventory', width: 120, sorter: (a, b) => a.inventory - b.inventory },
|
||||
{
|
||||
title: '库存',
|
||||
dataIndex: 'inventory',
|
||||
width: 120,
|
||||
sorter: (a, b) => a.inventory - b.inventory,
|
||||
},
|
||||
{
|
||||
title: '发货限制',
|
||||
width: 150,
|
||||
@@ -1186,9 +1255,9 @@ function MetricCard({ label, value, detail }: { label: string; value: string; de
|
||||
|
||||
function normalizeRoutingPriority(value: unknown) {
|
||||
const rawItems = Array.isArray(value) ? value : []
|
||||
const allowed = ROUTING_EXECUTORS
|
||||
.filter((executor) => executor.key !== 'manual_dispatch')
|
||||
.map((executor) => executor.key)
|
||||
const allowed = ROUTING_EXECUTORS.filter((executor) => executor.key !== 'manual_dispatch').map(
|
||||
(executor) => executor.key,
|
||||
)
|
||||
const seen = new Set<string>()
|
||||
const items = rawItems
|
||||
.map((item) => String(item || '').trim())
|
||||
@@ -1198,16 +1267,20 @@ function normalizeRoutingPriority(value: unknown) {
|
||||
}
|
||||
|
||||
function normalizeRoutingExecutors(value: unknown): AdminFulfillmentRoutingConfig['executors'] {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as AdminFulfillmentRoutingConfig['executors']
|
||||
: {}
|
||||
const source =
|
||||
value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as AdminFulfillmentRoutingConfig['executors'])
|
||||
: {}
|
||||
|
||||
return ROUTING_EXECUTORS.reduce<AdminFulfillmentRoutingConfig['executors']>((result, executor) => {
|
||||
result[executor.key] = {
|
||||
enabled: source[executor.key]?.enabled !== false,
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
return ROUTING_EXECUTORS.reduce<AdminFulfillmentRoutingConfig['executors']>(
|
||||
(result, executor) => {
|
||||
result[executor.key] = {
|
||||
enabled: source[executor.key]?.enabled !== false,
|
||||
}
|
||||
return result
|
||||
},
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeRoutingRule(rule: AdminFulfillmentRoutingRule): EditableRoutingRule {
|
||||
@@ -1272,7 +1345,9 @@ function normalizeEditableRule(rule: AdminCloudtentaclesOverrideRule): EditableO
|
||||
}
|
||||
}
|
||||
|
||||
function createDeliveryItem(skuItems: AdminCloudtentaclesSkuItem[]): AdminCloudtentaclesOverrideDeliveryItem {
|
||||
function createDeliveryItem(
|
||||
skuItems: AdminCloudtentaclesSkuItem[],
|
||||
): AdminCloudtentaclesOverrideDeliveryItem {
|
||||
const firstSku = skuItems[0]
|
||||
return {
|
||||
cloudSkuId: Number(firstSku?.id || 0) || 0,
|
||||
@@ -1300,7 +1375,9 @@ function normalizeMatchName(value: unknown) {
|
||||
.trim()
|
||||
}
|
||||
|
||||
function formatSourceLabel(source: Pick<AdminCloudtentaclesSourceItem, 'key' | 'label' | 'username'>) {
|
||||
function formatSourceLabel(
|
||||
source: Pick<AdminCloudtentaclesSourceItem, 'key' | 'label' | 'username'>,
|
||||
) {
|
||||
return source.label || source.username || source.key
|
||||
}
|
||||
|
||||
@@ -1316,4 +1393,3 @@ function formatSkuOption(sku: AdminCloudtentaclesSkuItem) {
|
||||
function createRuleId() {
|
||||
return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
|
||||
@@ -124,9 +124,13 @@ export default function AdminPlatformShopsPage() {
|
||||
const activeTab = normalizePlatformTab(searchParams.get('tab'))
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const [notificationConfig, setNotificationConfig] = useState<NotificationConfigResponse | null>(null)
|
||||
const [notificationConfig, setNotificationConfig] = useState<NotificationConfigResponse | null>(
|
||||
null,
|
||||
)
|
||||
const [scheduledJobs, setScheduledJobs] = useState<ScheduledJobsState | null>(null)
|
||||
const [industryConfig, setIndustryConfig] = useState<AdminKuaishouIndustryConfigResponse | null>(null)
|
||||
const [industryConfig, setIndustryConfig] = useState<AdminKuaishouIndustryConfigResponse | null>(
|
||||
null,
|
||||
)
|
||||
const [feifeiConfig, setFeifeiConfig] = useState<AdminKuaishouFeifeiConfigResponse | null>(null)
|
||||
const [cloudtentaclesConfig, setCloudtentaclesConfig] =
|
||||
useState<AdminCloudtentaclesSourceConfigResponse | null>(null)
|
||||
@@ -213,25 +217,23 @@ export default function AdminPlatformShopsPage() {
|
||||
{
|
||||
key: 'notifications',
|
||||
label: '内部通知',
|
||||
children: notificationConfig && scheduledJobs ? (
|
||||
<NotificationPanel
|
||||
notificationConfig={notificationConfig}
|
||||
scheduledJobs={scheduledJobs}
|
||||
onNotificationChange={setNotificationConfig}
|
||||
onScheduledJobsChange={setScheduledJobs}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="通知配置未加载" />
|
||||
),
|
||||
children:
|
||||
notificationConfig && scheduledJobs ? (
|
||||
<NotificationPanel
|
||||
notificationConfig={notificationConfig}
|
||||
scheduledJobs={scheduledJobs}
|
||||
onNotificationChange={setNotificationConfig}
|
||||
onScheduledJobsChange={setScheduledJobs}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="通知配置未加载" />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'kuaishouIndustry',
|
||||
label: '行业电子凭证',
|
||||
children: industryConfig ? (
|
||||
<KuaishouIndustryPanel
|
||||
config={industryConfig}
|
||||
onChange={setIndustryConfig}
|
||||
/>
|
||||
<KuaishouIndustryPanel config={industryConfig} onChange={setIndustryConfig} />
|
||||
) : (
|
||||
<Empty description="快手行业电子凭证配置未加载" />
|
||||
),
|
||||
@@ -399,9 +401,7 @@ function NotificationPanel({
|
||||
function updateJob(index: number, nextJob: AdminScheduledJobItem) {
|
||||
updateJobs({
|
||||
...scheduledJobs.source,
|
||||
jobs: jobsWithMergedAccounts.map((item, jobIndex) =>
|
||||
jobIndex === index ? nextJob : item,
|
||||
),
|
||||
jobs: jobsWithMergedAccounts.map((item, jobIndex) => (jobIndex === index ? nextJob : item)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,10 +411,19 @@ function NotificationPanel({
|
||||
title="通知渠道"
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button icon={<SendOutlined />} loading={testingNotification} onClick={testNotification}>
|
||||
<Button
|
||||
icon={<SendOutlined />}
|
||||
loading={testingNotification}
|
||||
onClick={testNotification}
|
||||
>
|
||||
发送测试
|
||||
</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={savingNotification} onClick={saveNotification}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
loading={savingNotification}
|
||||
onClick={saveNotification}
|
||||
>
|
||||
保存通知
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -554,8 +563,14 @@ function RecipientList<T extends { id: string; name: string; enabled: boolean }>
|
||||
}: {
|
||||
title: string
|
||||
secretField: 'deviceKey' | 'apiKey'
|
||||
items: Array<T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>>
|
||||
onChange: (items: Array<T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>>) => void
|
||||
items: Array<
|
||||
T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>
|
||||
>
|
||||
onChange: (
|
||||
items: Array<
|
||||
T & Partial<Record<'deviceKey' | 'apiKey' | 'deviceKeyMasked' | 'apiKeyMasked', string>>
|
||||
>,
|
||||
) => void
|
||||
}) {
|
||||
function addRecipient() {
|
||||
onChange([
|
||||
@@ -569,12 +584,24 @@ function RecipientList<T extends { id: string; name: string; enabled: boolean }>
|
||||
])
|
||||
}
|
||||
|
||||
function updateRecipient(index: number, patch: Partial<T & Record<'deviceKey' | 'apiKey', string>>) {
|
||||
function updateRecipient(
|
||||
index: number,
|
||||
patch: Partial<T & Record<'deviceKey' | 'apiKey', string>>,
|
||||
) {
|
||||
onChange(items.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)))
|
||||
}
|
||||
|
||||
return (
|
||||
<Card size="small" title={title} className="platform-section-gap" extra={<Button icon={<PlusOutlined />} onClick={addRecipient}>新增</Button>}>
|
||||
<Card
|
||||
size="small"
|
||||
title={title}
|
||||
className="platform-section-gap"
|
||||
extra={
|
||||
<Button icon={<PlusOutlined />} onClick={addRecipient}>
|
||||
新增
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{items.length === 0 ? (
|
||||
<Empty description="暂无接收人" />
|
||||
) : (
|
||||
@@ -583,23 +610,37 @@ function RecipientList<T extends { id: string; name: string; enabled: boolean }>
|
||||
<div key={item.id || index} className="platform-inline-row">
|
||||
<Switch
|
||||
checked={item.enabled !== false}
|
||||
onChange={(enabled) => updateRecipient(index, { enabled } as Partial<T & Record<'deviceKey' | 'apiKey', string>>)}
|
||||
onChange={(enabled) =>
|
||||
updateRecipient(index, { enabled } as Partial<
|
||||
T & Record<'deviceKey' | 'apiKey', string>
|
||||
>)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
value={item.name}
|
||||
placeholder="名称"
|
||||
onChange={(event) => updateRecipient(index, { name: event.target.value } as Partial<T & Record<'deviceKey' | 'apiKey', string>>)}
|
||||
onChange={(event) =>
|
||||
updateRecipient(index, { name: event.target.value } as Partial<
|
||||
T & Record<'deviceKey' | 'apiKey', string>
|
||||
>)
|
||||
}
|
||||
/>
|
||||
<Input.Password
|
||||
value={String(item[secretField] || '')}
|
||||
placeholder={String(item[`${secretField}Masked` as keyof typeof item] || secretField)}
|
||||
placeholder={String(
|
||||
item[`${secretField}Masked` as keyof typeof item] || secretField,
|
||||
)}
|
||||
onChange={(event) =>
|
||||
updateRecipient(index, {
|
||||
[secretField]: event.target.value,
|
||||
} as Partial<T & Record<'deviceKey' | 'apiKey', string>>)
|
||||
}
|
||||
/>
|
||||
<Button danger icon={<DeleteOutlined />} onClick={() => onChange(items.filter((_, itemIndex) => itemIndex !== index))} />
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => onChange(items.filter((_, itemIndex) => itemIndex !== index))}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
@@ -634,9 +675,7 @@ function ScheduledJobCard({
|
||||
onChange={(enabled) => onChange({ ...job, enabled })}
|
||||
/>
|
||||
<span>{formatScheduledJobTitle(job)}</span>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>
|
||||
{job.enabled ? '已启用' : '已停用'}
|
||||
</Tag>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>{job.enabled ? '已启用' : '已停用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
@@ -660,9 +699,7 @@ function ScheduledJobCard({
|
||||
label="每次扫描数量"
|
||||
value={Number(job.config?.scanLimit ?? 50)}
|
||||
min={1}
|
||||
onChange={(scanLimit) =>
|
||||
onChange({ ...job, config: { ...job.config, scanLimit } })
|
||||
}
|
||||
onChange={(scanLimit) => onChange({ ...job, config: { ...job.config, scanLimit } })}
|
||||
/>
|
||||
</div>
|
||||
{runtime ? (
|
||||
@@ -701,9 +738,7 @@ function ScheduledJobCard({
|
||||
onChange={(enabled) => onChange({ ...job, enabled })}
|
||||
/>
|
||||
<span>{formatScheduledJobTitle(job)}</span>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>
|
||||
{job.enabled ? '已启用' : '已停用'}
|
||||
</Tag>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>{job.enabled ? '已启用' : '已停用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
@@ -713,8 +748,7 @@ function ScheduledJobCard({
|
||||
}
|
||||
>
|
||||
<Typography.Paragraph type="secondary" style={{ marginBottom: 12 }}>
|
||||
将有押金工单验收通过后进入待解冻的押金,在到期后自动转入可用余额(验收后 3
|
||||
天到账)。
|
||||
将有押金工单验收通过后进入待解冻的押金,在到期后自动转入可用余额(验收后 3 天到账)。
|
||||
</Typography.Paragraph>
|
||||
<div className="platform-form-grid">
|
||||
<NumberField
|
||||
@@ -727,9 +761,7 @@ function ScheduledJobCard({
|
||||
label="每次解冻数量"
|
||||
value={Number(job.config?.scanLimit ?? 100)}
|
||||
min={1}
|
||||
onChange={(scanLimit) =>
|
||||
onChange({ ...job, config: { ...job.config, scanLimit } })
|
||||
}
|
||||
onChange={(scanLimit) => onChange({ ...job, config: { ...job.config, scanLimit } })}
|
||||
/>
|
||||
</div>
|
||||
{runtime ? (
|
||||
@@ -758,7 +790,10 @@ function ScheduledJobCard({
|
||||
const accounts = job.config?.accounts || []
|
||||
const defaultThreshold = Number(job.config?.assetThreshold ?? 500)
|
||||
|
||||
function updateAccount(sourceKey: string, patch: Partial<AdminScheduledJobCloudtentaclesAccount>) {
|
||||
function updateAccount(
|
||||
sourceKey: string,
|
||||
patch: Partial<AdminScheduledJobCloudtentaclesAccount>,
|
||||
) {
|
||||
onChange({
|
||||
...job,
|
||||
config: {
|
||||
@@ -849,7 +884,8 @@ function ScheduledJobCard({
|
||||
{formatHealthStatus(accountRuntime.status)}
|
||||
</Tag>
|
||||
<Typography.Text type="secondary">
|
||||
余额 {accountRuntime.asset ?? '-'} / 阈值 {accountRuntime.threshold ?? account.assetThreshold}
|
||||
余额 {accountRuntime.asset ?? '-'} / 阈值{' '}
|
||||
{accountRuntime.threshold ?? account.assetThreshold}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Typography.Text type="secondary">
|
||||
@@ -869,12 +905,19 @@ function ScheduledJobCard({
|
||||
size="small"
|
||||
title={
|
||||
<Space wrap>
|
||||
<Switch checked={job.enabled !== false} onChange={(enabled) => onChange({ ...job, enabled })} />
|
||||
<Switch
|
||||
checked={job.enabled !== false}
|
||||
onChange={(enabled) => onChange({ ...job, enabled })}
|
||||
/>
|
||||
<span>{formatScheduledJobTitle(job)}</span>
|
||||
<Tag color={job.enabled ? 'green' : 'default'}>{job.enabled ? '已启用' : '已停用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={<Button icon={<ReloadOutlined />} loading={running} onClick={onRun}>立即检查</Button>}
|
||||
extra={
|
||||
<Button icon={<ReloadOutlined />} loading={running} onClick={onRun}>
|
||||
立即检查
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="platform-form-grid">
|
||||
<NumberField
|
||||
@@ -909,15 +952,13 @@ function ScheduledJobCard({
|
||||
}
|
||||
showIcon
|
||||
message={runtime.lastMessage || '尚未运行'}
|
||||
description={
|
||||
[
|
||||
`已检查 ${runtime.lastCheckedCount ?? 0} / ${accounts.length}`,
|
||||
`预警 ${runtime.lastLowAssetCount ?? 0}`,
|
||||
`异常 ${runtime.lastFailedCount ?? 0}`,
|
||||
`上次:${formatAdminDateTime(runtime.lastFinishedAt || runtime.lastRunAt)}`,
|
||||
`下次:${formatAdminDateTime(runtime.nextRunAt)}`,
|
||||
].join(' · ')
|
||||
}
|
||||
description={[
|
||||
`已检查 ${runtime.lastCheckedCount ?? 0} / ${accounts.length}`,
|
||||
`预警 ${runtime.lastLowAssetCount ?? 0}`,
|
||||
`异常 ${runtime.lastFailedCount ?? 0}`,
|
||||
`上次:${formatAdminDateTime(runtime.lastFinishedAt || runtime.lastRunAt)}`,
|
||||
`下次:${formatAdminDateTime(runtime.nextRunAt)}`,
|
||||
].join(' · ')}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -953,9 +994,7 @@ function mergeScheduledJobAccounts(
|
||||
sourceKey,
|
||||
label: String(configured?.label || item.label || sourceKey).trim(),
|
||||
enabled: configured ? configured.enabled !== false : item.enabled !== false,
|
||||
assetThreshold: Number(
|
||||
configured?.assetThreshold ?? defaultAssetThreshold,
|
||||
),
|
||||
assetThreshold: Number(configured?.assetThreshold ?? defaultAssetThreshold),
|
||||
}
|
||||
})
|
||||
const mergedKeys = new Set(merged.map((item) => item.sourceKey))
|
||||
@@ -997,13 +1036,15 @@ function formatMonitorAccountMeta(
|
||||
return '账号配置不存在'
|
||||
}
|
||||
|
||||
return [
|
||||
source.username ? `登录名 ${source.username}` : '',
|
||||
source.phoneMasked ? `手机 ${source.phoneMasked}` : '',
|
||||
source.hasToken ? 'Token 已保存' : '未登录',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ') || account.sourceKey
|
||||
return (
|
||||
[
|
||||
source.username ? `登录名 ${source.username}` : '',
|
||||
source.phoneMasked ? `手机 ${source.phoneMasked}` : '',
|
||||
source.hasToken ? 'Token 已保存' : '未登录',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ') || account.sourceKey
|
||||
)
|
||||
}
|
||||
|
||||
function getAccountRuntime(
|
||||
@@ -1190,9 +1231,7 @@ function KuaishouIndustryPanel({
|
||||
|
||||
function updateShop(index: number, patch: Partial<AdminKuaishouIndustryShopConfig>) {
|
||||
updateSource({
|
||||
shops: shops.map((shop, shopIndex) =>
|
||||
shopIndex === index ? { ...shop, ...patch } : shop,
|
||||
),
|
||||
shops: shops.map((shop, shopIndex) => (shopIndex === index ? { ...shop, ...patch } : shop)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1281,7 +1320,9 @@ function KuaishouIndustryPanel({
|
||||
return (
|
||||
<div className="status-stack">
|
||||
<Tag color={status.color}>{status.label}</Tag>
|
||||
{row.accessTokenMasked ? <Typography.Text type="secondary">{row.accessTokenMasked}</Typography.Text> : null}
|
||||
{row.accessTokenMasked ? (
|
||||
<Typography.Text type="secondary">{row.accessTokenMasked}</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -1291,8 +1332,12 @@ function KuaishouIndustryPanel({
|
||||
minWidth: 220,
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
<span>{row.accessTokenExpiresAt ? formatAdminDateTime(row.accessTokenExpiresAt) : '-'}</span>
|
||||
<span className="muted">{formatIndustryTokenCountdown(row.accessTokenExpiresInSeconds)}</span>
|
||||
<span>
|
||||
{row.accessTokenExpiresAt ? formatAdminDateTime(row.accessTokenExpiresAt) : '-'}
|
||||
</span>
|
||||
<span className="muted">
|
||||
{formatIndustryTokenCountdown(row.accessTokenExpiresInSeconds)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -1325,10 +1370,18 @@ function KuaishouIndustryPanel({
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Space wrap>
|
||||
<Button size="small" icon={<CopyOutlined />} onClick={() => copyAuthorizationUrl(row)}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => copyAuthorizationUrl(row)}
|
||||
>
|
||||
复制授权
|
||||
</Button>
|
||||
<Button size="small" icon={<LinkOutlined />} onClick={() => openAuthorizationUrl(row)}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => openAuthorizationUrl(row)}
|
||||
>
|
||||
打开授权
|
||||
</Button>
|
||||
<Button
|
||||
@@ -1531,25 +1584,33 @@ function KuaishouIndustryPanel({
|
||||
<Input.Password
|
||||
value={row.refreshToken}
|
||||
placeholder={row.refreshTokenMasked || '留空保持原值'}
|
||||
onChange={(event) => updateShop(index, { refreshToken: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateShop(index, { refreshToken: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<LabeledInput
|
||||
label="accessToken 过期时间"
|
||||
value={row.accessTokenExpiresAt}
|
||||
onChange={(accessTokenExpiresAt) => updateShop(index, { accessTokenExpiresAt })}
|
||||
onChange={(accessTokenExpiresAt) =>
|
||||
updateShop(index, { accessTokenExpiresAt })
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="refreshToken 过期时间"
|
||||
value={row.refreshTokenExpiresAt}
|
||||
onChange={(refreshTokenExpiresAt) => updateShop(index, { refreshTokenExpiresAt })}
|
||||
onChange={(refreshTokenExpiresAt) =>
|
||||
updateShop(index, { refreshTokenExpiresAt })
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text type="secondary">最近错误</Typography.Text>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
value={row.lastRefreshError}
|
||||
onChange={(event) => updateShop(index, { lastRefreshError: event.target.value })}
|
||||
onChange={(event) =>
|
||||
updateShop(index, { lastRefreshError: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1596,7 +1657,9 @@ function KuaishouFeifeiPlatformPanel({
|
||||
try {
|
||||
const response = await syncAdminKuaishouFeifeiProducts({ status: 'on_sale' })
|
||||
onChange(response.data)
|
||||
showSuccess(`商品映射已同步:商品 ${response.data.sync.productCount} 个,规则 ${response.data.sync.ruleCount} 条`)
|
||||
showSuccess(
|
||||
`商品映射已同步:商品 ${response.data.sync.productCount} 个,规则 ${response.data.sync.ruleCount} 条`,
|
||||
)
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '同步 kuaishou-feifei 商品映射失败')
|
||||
} finally {
|
||||
@@ -1636,9 +1699,20 @@ function KuaishouFeifeiPlatformPanel({
|
||||
return (
|
||||
<section className="platform-panel-stack">
|
||||
<div className="metric-grid three">
|
||||
<MetricCard label="运行状态" value={config.effective.enabled ? '已启用' : '停用'} detail={source.enabled ? '源配置启用' : '源配置停用'} />
|
||||
<MetricCard label="API 凭据" value={config.effective.hasAppKey && config.effective.hasAppSecret ? '已配置' : '待补全'} />
|
||||
<MetricCard label="启用规则" value={String(enabledRules.length)} detail={`共 ${source.productRules.length} 条`} />
|
||||
<MetricCard
|
||||
label="运行状态"
|
||||
value={config.effective.enabled ? '已启用' : '停用'}
|
||||
detail={source.enabled ? '源配置启用' : '源配置停用'}
|
||||
/>
|
||||
<MetricCard
|
||||
label="API 凭据"
|
||||
value={config.effective.hasAppKey && config.effective.hasAppSecret ? '已配置' : '待补全'}
|
||||
/>
|
||||
<MetricCard
|
||||
label="启用规则"
|
||||
value={String(enabledRules.length)}
|
||||
detail={`共 ${source.productRules.length} 条`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
@@ -1655,31 +1729,59 @@ function KuaishouFeifeiPlatformPanel({
|
||||
}
|
||||
>
|
||||
<div className="platform-form-grid">
|
||||
<FieldSwitch label="启用" checked={source.enabled} onChange={(enabled) => updateSource({ enabled })} />
|
||||
<FieldSwitch
|
||||
label="启用"
|
||||
checked={source.enabled}
|
||||
onChange={(enabled) => updateSource({ enabled })}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text type="secondary">接口地址</Typography.Text>
|
||||
<Input value={source.baseUrl} onChange={(event) => updateSource({ baseUrl: event.target.value })} />
|
||||
<Input
|
||||
value={source.baseUrl}
|
||||
onChange={(event) => updateSource({ baseUrl: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text type="secondary">App Key</Typography.Text>
|
||||
<Input value={source.appKey} onChange={(event) => updateSource({ appKey: event.target.value })} />
|
||||
<Input
|
||||
value={source.appKey}
|
||||
onChange={(event) => updateSource({ appKey: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text type="secondary">App Secret</Typography.Text>
|
||||
<Input.Password value={source.appSecret} onChange={(event) => updateSource({ appSecret: event.target.value })} />
|
||||
<Input.Password
|
||||
value={source.appSecret}
|
||||
onChange={(event) => updateSource({ appSecret: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<NumberField label="超时毫秒" value={source.timeoutMs} min={1000} onChange={(timeoutMs) => updateSource({ timeoutMs })} />
|
||||
<NumberField
|
||||
label="超时毫秒"
|
||||
value={source.timeoutMs}
|
||||
min={1000}
|
||||
onChange={(timeoutMs) => updateSource({ timeoutMs })}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text type="secondary">通知地址</Typography.Text>
|
||||
<Input value={source.notifyUrl} onChange={(event) => updateSource({ notifyUrl: event.target.value })} />
|
||||
<Input
|
||||
value={source.notifyUrl}
|
||||
onChange={(event) => updateSource({ notifyUrl: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="商品映射">
|
||||
<Space.Compact className="full-width">
|
||||
<Input value={matchInput} placeholder="91 商品名" onChange={(event) => setMatchInput(event.target.value)} onPressEnter={previewMatch} />
|
||||
<Button icon={<SearchOutlined />} loading={matching} onClick={previewMatch}>预览命中</Button>
|
||||
<Input
|
||||
value={matchInput}
|
||||
placeholder="91 商品名"
|
||||
onChange={(event) => setMatchInput(event.target.value)}
|
||||
onPressEnter={previewMatch}
|
||||
/>
|
||||
<Button icon={<SearchOutlined />} loading={matching} onClick={previewMatch}>
|
||||
预览命中
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
{matchResult ? (
|
||||
<Alert
|
||||
@@ -1687,7 +1789,11 @@ function KuaishouFeifeiPlatformPanel({
|
||||
type={matchResult.matched ? 'success' : 'warning'}
|
||||
showIcon
|
||||
message={matchResult.matched ? '已命中' : '未命中'}
|
||||
description={matchResult.match ? `${matchResult.match.productName} -> ${matchResult.match.productCode}` : matchResult.normalizedProductName}
|
||||
description={
|
||||
matchResult.match
|
||||
? `${matchResult.match.productName} -> ${matchResult.match.productCode}`
|
||||
: matchResult.normalizedProductName
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -1702,35 +1808,50 @@ function KuaishouFeifeiPlatformPanel({
|
||||
title: '启用',
|
||||
width: 80,
|
||||
render: (_, row, index) => (
|
||||
<Switch checked={row.enabled !== false} onChange={(enabled) => updateRule(index, { enabled })} />
|
||||
<Switch
|
||||
checked={row.enabled !== false}
|
||||
onChange={(enabled) => updateRule(index, { enabled })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '商品名',
|
||||
minWidth: 260,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.productName} onChange={(event) => updateRule(index, { productName: event.target.value })} />
|
||||
<Input
|
||||
value={row.productName}
|
||||
onChange={(event) => updateRule(index, { productName: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'product_code',
|
||||
minWidth: 160,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.productCode} onChange={(event) => updateRule(index, { productCode: event.target.value })} />
|
||||
<Input
|
||||
value={row.productCode}
|
||||
onChange={(event) => updateRule(index, { productCode: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '展示名',
|
||||
minWidth: 220,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.skuName} onChange={(event) => updateRule(index, { skuName: event.target.value })} />
|
||||
<Input
|
||||
value={row.skuName}
|
||||
onChange={(event) => updateRule(index, { skuName: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
minWidth: 180,
|
||||
render: (_, row, index) => (
|
||||
<Input value={row.notes} onChange={(event) => updateRule(index, { notes: event.target.value })} />
|
||||
<Input
|
||||
value={row.notes}
|
||||
onChange={(event) => updateRule(index, { notes: event.target.value })}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
@@ -1773,9 +1894,10 @@ function CloudtentaclesPlatformPanel({
|
||||
sources: config.sources,
|
||||
})
|
||||
onChange(response.data)
|
||||
const nextKey = selectedSourceKey && response.data.sources.some((item) => item.key === selectedSourceKey)
|
||||
? selectedSourceKey
|
||||
: response.data.sources[0]?.key || ''
|
||||
const nextKey =
|
||||
selectedSourceKey && response.data.sources.some((item) => item.key === selectedSourceKey)
|
||||
? selectedSourceKey
|
||||
: response.data.sources[0]?.key || ''
|
||||
setSelectedSourceKey(nextKey)
|
||||
showSuccess('kuaishou-lewan 账号配置已保存')
|
||||
} catch (error) {
|
||||
@@ -1791,9 +1913,8 @@ function CloudtentaclesPlatformPanel({
|
||||
await deleteAdminCloudtentaclesSource(sourceKey)
|
||||
const response = await fetchAdminCloudtentaclesSourceConfig()
|
||||
onChange(response.data)
|
||||
const nextKey = selectedSourceKey === sourceKey
|
||||
? response.data.sources[0]?.key || ''
|
||||
: selectedSourceKey
|
||||
const nextKey =
|
||||
selectedSourceKey === sourceKey ? response.data.sources[0]?.key || '' : selectedSourceKey
|
||||
setSelectedSourceKey(
|
||||
response.data.sources.some((item) => item.key === nextKey)
|
||||
? nextKey
|
||||
@@ -1906,9 +2027,18 @@ function CloudtentaclesPlatformPanel({
|
||||
title="kuaishou-lewan 履约账号"
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Switch checked={config.enabled} checkedChildren="启用" unCheckedChildren="停用" onChange={(enabled) => updateConfig({ enabled })} />
|
||||
<Button icon={<PlusOutlined />} onClick={addSource}>新增账号</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={saveConfig}>保存账号</Button>
|
||||
<Switch
|
||||
checked={config.enabled}
|
||||
checkedChildren="启用"
|
||||
unCheckedChildren="停用"
|
||||
onChange={(enabled) => updateConfig({ enabled })}
|
||||
/>
|
||||
<Button icon={<PlusOutlined />} onClick={addSource}>
|
||||
新增账号
|
||||
</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={saveConfig}>
|
||||
保存账号
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
@@ -1932,8 +2062,20 @@ function CloudtentaclesPlatformPanel({
|
||||
<strong>{formatCloudtentaclesSourceLabel(source)}</strong>
|
||||
<small>{source.username || source.key}</small>
|
||||
</span>
|
||||
<Tag color={session?.hasToken ? 'green' : source.enabled === false ? 'default' : 'orange'}>
|
||||
{session?.hasToken ? '已登录' : source.enabled === false ? '已停用' : '未登录'}
|
||||
<Tag
|
||||
color={
|
||||
session?.hasToken
|
||||
? 'green'
|
||||
: source.enabled === false
|
||||
? 'default'
|
||||
: 'orange'
|
||||
}
|
||||
>
|
||||
{session?.hasToken
|
||||
? '已登录'
|
||||
: source.enabled === false
|
||||
? '已停用'
|
||||
: '未登录'}
|
||||
</Tag>
|
||||
</button>
|
||||
)
|
||||
@@ -1943,7 +2085,11 @@ function CloudtentaclesPlatformPanel({
|
||||
|
||||
<Card
|
||||
size="small"
|
||||
title={selectedSource ? `编辑账号:${formatCloudtentaclesSourceLabel(selectedSource)}` : '编辑账号'}
|
||||
title={
|
||||
selectedSource
|
||||
? `编辑账号:${formatCloudtentaclesSourceLabel(selectedSource)}`
|
||||
: '编辑账号'
|
||||
}
|
||||
extra={
|
||||
selectedSource ? (
|
||||
<Button
|
||||
@@ -2087,9 +2233,7 @@ function CloudtentaclesPlatformPanel({
|
||||
...nextConfig.data.sources,
|
||||
...config.sources.filter(
|
||||
(source) =>
|
||||
!nextConfig.data.sources.some(
|
||||
(item) => item.key === source.key,
|
||||
),
|
||||
!nextConfig.data.sources.some((item) => item.key === source.key),
|
||||
),
|
||||
],
|
||||
})
|
||||
@@ -2158,7 +2302,9 @@ function CloudtentaclesPlatformPanel({
|
||||
查看号码列表
|
||||
</Button>
|
||||
</Space>
|
||||
{debugResult ? <pre className="json-preview">{JSON.stringify(debugResult, null, 2)}</pre> : null}
|
||||
{debugResult ? (
|
||||
<pre className="json-preview">{JSON.stringify(debugResult, null, 2)}</pre>
|
||||
) : null}
|
||||
{vnItems !== null ? (
|
||||
<Table<AdminCloudtentaclesVnListItem>
|
||||
className="platform-section-gap"
|
||||
@@ -2195,7 +2341,13 @@ function CloudtentaclesPlatformPanel({
|
||||
dataIndex: 'countTime',
|
||||
render: (countTime: number) => (
|
||||
<Typography.Text
|
||||
type={Number(countTime) < 300 ? 'danger' : Number(countTime) < 900 ? 'warning' : undefined}
|
||||
type={
|
||||
Number(countTime) < 300
|
||||
? 'danger'
|
||||
: Number(countTime) < 900
|
||||
? 'warning'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{formatCountdownSeconds(Number(countTime))}
|
||||
</Typography.Text>
|
||||
@@ -2217,11 +2369,7 @@ function CloudtentaclesPlatformPanel({
|
||||
cancelText="取消"
|
||||
onConfirm={() => void backVn(item)}
|
||||
>
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
loading={vnBackingId === item.id}
|
||||
>
|
||||
<Button danger size="small" loading={vnBackingId === item.id}>
|
||||
退号
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
@@ -2237,7 +2385,9 @@ function CloudtentaclesPlatformPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function formatCloudtentaclesSourceLabel(source: Pick<AdminCloudtentaclesSourceItem, 'label' | 'username' | 'key'>) {
|
||||
function formatCloudtentaclesSourceLabel(
|
||||
source: Pick<AdminCloudtentaclesSourceItem, 'label' | 'username' | 'key'>,
|
||||
) {
|
||||
return String(source.label || source.username || source.key || '').trim() || source.key
|
||||
}
|
||||
|
||||
@@ -2606,8 +2756,20 @@ function AffiliateDashPlatformPanel() {
|
||||
{ title: 'sku', dataIndex: 'sku', key: 'sku', width: 180 },
|
||||
{ title: '名称', dataIndex: 'displayName', key: 'displayName' },
|
||||
{ title: '单价', dataIndex: 'priceAmount', key: 'priceAmount', width: 90 },
|
||||
{ title: '库存', dataIndex: 'stock', key: 'stock', width: 70, render: (v: number) => (v === -1 ? '不限' : v) },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 80, render: (v: string) => <Tag color={v === 'active' ? 'green' : 'red'}>{v}</Tag> },
|
||||
{
|
||||
title: '库存',
|
||||
dataIndex: 'stock',
|
||||
key: 'stock',
|
||||
width: 70,
|
||||
render: (v: number) => (v === -1 ? '不限' : v),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
render: (v: string) => <Tag color={v === 'active' ? 'green' : 'red'}>{v}</Tag>,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -2615,15 +2777,21 @@ function AffiliateDashPlatformPanel() {
|
||||
title="affiliate-dash 发货平台"
|
||||
extra={
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadWallet}>刷新钱包</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => loadProducts(productPage)}>刷新商品</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadWallet}>
|
||||
刷新钱包
|
||||
</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => loadProducts(productPage)}>
|
||||
刷新商品
|
||||
</Button>
|
||||
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={saveConfig}>
|
||||
保存配置
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{errorMessage ? <Alert type="error" message={errorMessage} showIcon style={{ marginBottom: 16 }} /> : null}
|
||||
{errorMessage ? (
|
||||
<Alert type="error" message={errorMessage} showIcon style={{ marginBottom: 16 }} />
|
||||
) : null}
|
||||
|
||||
<Spin spinning={loading}>
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
@@ -2637,25 +2805,53 @@ function AffiliateDashPlatformPanel() {
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>BASE_URL</span>
|
||||
<Input style={{ width: 320 }} value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="https://skin.khhao.com" />
|
||||
<Input
|
||||
style={{ width: 320 }}
|
||||
value={baseUrl}
|
||||
onChange={(e) => setBaseUrl(e.target.value)}
|
||||
placeholder="https://skin.khhao.com"
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>App Key</span>
|
||||
<Input style={{ width: 320 }} value={appKey} onChange={(e) => setAppKey(e.target.value)} placeholder="ak_..." />
|
||||
<Input
|
||||
style={{ width: 320 }}
|
||||
value={appKey}
|
||||
onChange={(e) => setAppKey(e.target.value)}
|
||||
placeholder="ak_..."
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>App Secret</span>
|
||||
<Input.Password style={{ width: 320 }} value={appSecret} onChange={(e) => setAppSecret(e.target.value)} placeholder="sk_..." />
|
||||
<Input.Password
|
||||
style={{ width: 320 }}
|
||||
value={appSecret}
|
||||
onChange={(e) => setAppSecret(e.target.value)}
|
||||
placeholder="sk_..."
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>回调 Secret</span>
|
||||
<Input.Password style={{ width: 320 }} value={callbackSecret} onChange={(e) => setCallbackSecret(e.target.value)} placeholder="cb_..." />
|
||||
<Input.Password
|
||||
style={{ width: 320 }}
|
||||
value={callbackSecret}
|
||||
onChange={(e) => setCallbackSecret(e.target.value)}
|
||||
placeholder="cb_..."
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>超时(ms)</span>
|
||||
<InputNumber min={1000} value={timeoutMs} onChange={(v) => setTimeoutMs(Number(v || 10000))} />
|
||||
<InputNumber
|
||||
min={1000}
|
||||
value={timeoutMs}
|
||||
onChange={(v) => setTimeoutMs(Number(v || 10000))}
|
||||
/>
|
||||
<span>时间容差(s)</span>
|
||||
<InputNumber min={1} value={timestampToleranceSeconds} onChange={(v) => setTimestampToleranceSeconds(Number(v || 300))} />
|
||||
<InputNumber
|
||||
min={1}
|
||||
value={timestampToleranceSeconds}
|
||||
onChange={(v) => setTimestampToleranceSeconds(Number(v || 300))}
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<span>回调 URL(展示)</span>
|
||||
@@ -2664,7 +2860,9 @@ function AffiliateDashPlatformPanel() {
|
||||
{wallet ? (
|
||||
<Space wrap>
|
||||
<span>钱包余额</span>
|
||||
<Typography.Text strong>{wallet.availableBalance} {wallet.currency}</Typography.Text>
|
||||
<Typography.Text strong>
|
||||
{wallet.availableBalance} {wallet.currency}
|
||||
</Typography.Text>
|
||||
<span>(冻结 {wallet.frozenBalance})</span>
|
||||
</Space>
|
||||
) : null}
|
||||
@@ -2693,7 +2891,10 @@ function AffiliateDashPlatformPanel() {
|
||||
/>
|
||||
</Space>
|
||||
))}
|
||||
<Button icon={<PlusOutlined />} onClick={() => setSkuRows((rows) => [...rows, { productNo: '', sku: '' }])}>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setSkuRows((rows) => [...rows, { productNo: '', sku: '' }])}
|
||||
>
|
||||
新增映射
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -2708,7 +2909,9 @@ function AffiliateDashPlatformPanel() {
|
||||
value={matchInput}
|
||||
onChange={(e) => setMatchInput(e.target.value)}
|
||||
/>
|
||||
<Button icon={<SearchOutlined />} onClick={previewMatch}>测试映射</Button>
|
||||
<Button icon={<SearchOutlined />} onClick={previewMatch}>
|
||||
测试映射
|
||||
</Button>
|
||||
{matchResult ? (
|
||||
<Typography.Text type={matchResult.matched ? 'success' : 'warning'}>
|
||||
{matchResult.matched
|
||||
|
||||
Reference in New Issue
Block a user