统一前后端代码格式化配置

This commit is contained in:
yml2213
2026-08-16 17:27:12 +08:00
parent 8705f6a6a1
commit 5c7c3e14e3
278 changed files with 6386 additions and 5500 deletions
@@ -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