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

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
@@ -21,8 +21,7 @@ export function AdminDatePicker({
placeholder = '请选择日期',
...rest
}: AdminDatePickerProps) {
const resolvedFormat =
format ?? (rest.showTime ? ADMIN_DATE_TIME_FORMAT : ADMIN_DATE_FORMAT)
const resolvedFormat = format ?? (rest.showTime ? ADMIN_DATE_TIME_FORMAT : ADMIN_DATE_FORMAT)
return <DatePicker format={resolvedFormat} placeholder={placeholder} {...rest} />
}
@@ -30,22 +29,14 @@ export function AdminDatePicker({
/**
* 后台统一日期范围:默认 YYYY-MM-DDshowTime 时 YYYY-MM-DD HH:mm:ss。
*/
export function AdminRangePicker({
format,
placeholder,
...rest
}: AdminRangePickerProps) {
export function AdminRangePicker({ format, placeholder, ...rest }: AdminRangePickerProps) {
const withTime = Boolean(rest.showTime)
const resolvedFormat = format ?? (withTime ? ADMIN_DATE_TIME_FORMAT : ADMIN_DATE_FORMAT)
const resolvedPlaceholder =
placeholder ?? (withTime ? ADMIN_DATE_TIME_RANGE_PLACEHOLDER : ADMIN_DATE_RANGE_PLACEHOLDER)
return (
<DatePicker.RangePicker
format={resolvedFormat}
placeholder={resolvedPlaceholder}
{...rest}
/>
<DatePicker.RangePicker format={resolvedFormat} placeholder={resolvedPlaceholder} {...rest} />
)
}
@@ -78,10 +78,7 @@ export default function ImageUpload({
thumbUrl: file.thumbnailUrl || file.url,
}))
async function uploadImages(
images: File[],
source: UploadSource = 'default',
) {
async function uploadImages(images: File[], source: UploadSource = 'default') {
const remaining = maxCount - valueRef.current.length
if (remaining <= 0) {
message.warning(`最多上传 ${maxCount} 张图片`)
@@ -102,10 +99,7 @@ export default function ImageUpload({
}
}
async function uploadFiles(
files: File[],
source: UploadSource = 'default',
) {
async function uploadFiles(files: File[], source: UploadSource = 'default') {
const images = files.filter((file) => file.type.startsWith('image/'))
if (images.length === 0) return
await uploadImages(images, source)
@@ -223,9 +217,7 @@ export default function ImageUpload({
maxCount={maxCount}
customRequest={customRequest}
onRemove={(file) => {
const next = valueRef.current.filter(
(item) => (item.objectKey || item.url) !== file.uid,
)
const next = valueRef.current.filter((item) => (item.objectKey || item.url) !== file.uid)
valueRef.current = next
onChange?.(next)
return true
+27 -27
View File
@@ -1,24 +1,24 @@
export const TASK_STATUS = {
PENDING_PAYMENT: "pending_payment",
PAID: "paid",
LINK_GENERATED: "link_generated",
CLAIMED: "claimed",
PENDING_BINDING_PREPARE: "pending_binding_prepare",
WAITING_BINDING: "waiting_binding",
ROLE_CONFIRMED: "role_confirmed",
REDEEMING: "redeeming",
DISPATCHED_PENDING_RETURN: "dispatched_pending_return",
COMPLETED: "completed",
REDEEMED: "redeemed",
RETRY_PENDING: "retry_pending",
MANUAL_REVIEW: "manual_review",
FAILED: "failed",
EXPIRED: "expired",
CLOSED: "closed",
} as const;
PENDING_PAYMENT: 'pending_payment',
PAID: 'paid',
LINK_GENERATED: 'link_generated',
CLAIMED: 'claimed',
PENDING_BINDING_PREPARE: 'pending_binding_prepare',
WAITING_BINDING: 'waiting_binding',
ROLE_CONFIRMED: 'role_confirmed',
REDEEMING: 'redeeming',
DISPATCHED_PENDING_RETURN: 'dispatched_pending_return',
COMPLETED: 'completed',
REDEEMED: 'redeemed',
RETRY_PENDING: 'retry_pending',
MANUAL_REVIEW: 'manual_review',
FAILED: 'failed',
EXPIRED: 'expired',
CLOSED: 'closed',
} as const
export type KnownTaskStatus = (typeof TASK_STATUS)[keyof typeof TASK_STATUS];
export type TaskStatus = KnownTaskStatus | (string & {});
export type KnownTaskStatus = (typeof TASK_STATUS)[keyof typeof TASK_STATUS]
export type TaskStatus = KnownTaskStatus | (string & {})
const CLAIM_INACTIVE_STATUSES = new Set<TaskStatus>([
TASK_STATUS.COMPLETED,
@@ -27,7 +27,7 @@ const CLAIM_INACTIVE_STATUSES = new Set<TaskStatus>([
TASK_STATUS.FAILED,
TASK_STATUS.EXPIRED,
TASK_STATUS.CLOSED,
]);
])
const KUAISHOU_CLOUD_RESULT_STATUSES = new Set<TaskStatus>([
TASK_STATUS.DISPATCHED_PENDING_RETURN,
@@ -35,25 +35,25 @@ const KUAISHOU_CLOUD_RESULT_STATUSES = new Set<TaskStatus>([
TASK_STATUS.REDEEMED,
TASK_STATUS.MANUAL_REVIEW,
TASK_STATUS.FAILED,
]);
])
export function normalizeTaskStatus(value: unknown): TaskStatus {
return String(value || "").trim() as TaskStatus;
return String(value || '').trim() as TaskStatus
}
export function isClaimInactiveTaskStatus(status: unknown): boolean {
return CLAIM_INACTIVE_STATUSES.has(normalizeTaskStatus(status));
return CLAIM_INACTIVE_STATUSES.has(normalizeTaskStatus(status))
}
export function isKuaishouCloudRoleConfirmedStatus(status: unknown): boolean {
return normalizeTaskStatus(status) === TASK_STATUS.ROLE_CONFIRMED;
return normalizeTaskStatus(status) === TASK_STATUS.ROLE_CONFIRMED
}
export function isKuaishouCloudCompletedStatus(status: unknown): boolean {
const normalized = normalizeTaskStatus(status);
return normalized === TASK_STATUS.COMPLETED || normalized === TASK_STATUS.REDEEMED;
const normalized = normalizeTaskStatus(status)
return normalized === TASK_STATUS.COMPLETED || normalized === TASK_STATUS.REDEEMED
}
export function hasKuaishouCloudRedeemResultStatus(status: unknown): boolean {
return KUAISHOU_CLOUD_RESULT_STATUSES.has(normalizeTaskStatus(status));
return KUAISHOU_CLOUD_RESULT_STATUSES.has(normalizeTaskStatus(status))
}
+1 -3
View File
@@ -75,9 +75,7 @@ export default function AdminLayout() {
key: 'group-dev',
type: 'group',
label: '开发',
children: [
{ key: '/admin/dev-mock', icon: <BugOutlined />, label: '开发 Mock' },
],
children: [{ key: '/admin/dev-mock', icon: <BugOutlined />, label: '开发 Mock' }],
})
}
+1 -5
View File
@@ -24,11 +24,7 @@ export function showError(message: string, options: MessageOptions = {}) {
})
}
export function showConfirm(
message: string,
title = '确认操作',
options: ModalFuncProps = {},
) {
export function showConfirm(message: string, title = '确认操作', options: ModalFuncProps = {}) {
return new Promise<void>((resolve, reject) => {
Modal.confirm({
title,
+7 -24
View File
@@ -37,13 +37,9 @@ http.interceptors.response.use(
},
(error) => {
const responseMessage =
typeof error?.response?.data?.msg === 'string'
? error.response.data.msg.trim()
: ''
typeof error?.response?.data?.msg === 'string' ? error.response.data.msg.trim() : ''
const fallbackMessage = resolveFallbackHttpMessage(error)
const normalizedError = new Error(
responseMessage || fallbackMessage,
) as Error & {
const normalizedError = new Error(responseMessage || fallbackMessage) as Error & {
errorCode?: string
status?: number
}
@@ -56,10 +52,7 @@ http.interceptors.response.use(
normalizedError.status = error.response.status
}
if (
error?.config?.url &&
String(error.config.url).startsWith('/api/v1/admin')
) {
if (error?.config?.url && String(error.config.url).startsWith('/api/v1/admin')) {
const status = Number(error?.response?.status || 0)
const errorCode = String(error?.response?.data?.errorCode || '').trim()
@@ -75,10 +68,7 @@ http.interceptors.response.use(
}
}
if (
error?.config?.url &&
String(error.config.url).startsWith('/api/v1/worker')
) {
if (error?.config?.url && String(error.config.url).startsWith('/api/v1/worker')) {
const status = Number(error?.response?.status || 0)
const errorCode = String(error?.response?.data?.errorCode || '').trim()
@@ -126,13 +116,8 @@ function resolveFallbackHttpMessage(error: unknown) {
return '网络连接错误'
}
if (
typeof (error as { response?: { statusText?: string } })?.response
?.statusText === 'string'
) {
return String(
(error as { response: { statusText: string } }).response.statusText,
).trim()
if (typeof (error as { response?: { statusText?: string } })?.response?.statusText === 'string') {
return String((error as { response: { statusText: string } }).response.statusText).trim()
}
return '接口请求失败'
@@ -158,9 +143,7 @@ function shouldClearWorkerSession(errorCode: string) {
].includes(errorCode)
}
function request<T>(
config: Parameters<typeof http.request<ApiEnvelope<T>>>[0],
) {
function request<T>(config: Parameters<typeof http.request<ApiEnvelope<T>>>[0]) {
return http.request<ApiEnvelope<T>, ApiEnvelope<T>>(config)
}
@@ -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
@@ -42,7 +42,7 @@ export function AffiliateDashClaimPanel({
)
const bindMismatch = Boolean(
affiliateDash.bindMismatch ||
(bound && boundAccount && expectedAccount && !boundAccountMatched),
(bound && boundAccount && expectedAccount && !boundAccountMatched),
)
const canSubmit = Boolean(bound && boundAccountMatched && !bindMismatch)
const displayBoundAccount = boundAccount || '-'
@@ -173,11 +173,7 @@ export function AffiliateDashClaimPanel({
)
}
export function AffiliateDashResultStep({
snapshot,
}: {
snapshot: ClaimSnapshot
}) {
export function AffiliateDashResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
const affiliateDash = snapshot.affiliateDash
const success = Boolean(affiliateDash && !snapshot.isRedeemFailed)
@@ -1,14 +1,6 @@
import {
CheckCircleOutlined,
ExclamationCircleOutlined,
LinkOutlined,
} from '@ant-design/icons'
import { CheckCircleOutlined, ExclamationCircleOutlined, LinkOutlined } from '@ant-design/icons'
import { Alert, Button, Card, Typography } from 'antd'
import type {
ClaimKuaishouFeifeiFlowInfo,
ClaimOrderInfo,
ClaimProductInfo,
} from '@/types/claim'
import type { ClaimKuaishouFeifeiFlowInfo, ClaimOrderInfo, ClaimProductInfo } from '@/types/claim'
import type { ClaimSnapshot } from './claim-snapshot'
import { InfoTile, UidEditRow } from './claim-shared'
@@ -59,11 +51,7 @@ export function FeifeiClaimPanel({
<InfoTile label="领取商品" value={product?.title || '-'} />
</div>
<Alert
type="info"
showIcon
message="将携带你填写的 UID 打开领取页,请确认游戏内角色一致。"
/>
<Alert type="info" showIcon message="将携带你填写的 UID 打开领取页,请确认游戏内角色一致。" />
<Button
type="primary"
@@ -91,7 +79,9 @@ export function FeifeiResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
{snapshot.resultVariant === 'warning' ? (
<ExclamationCircleOutlined className="claim-large-icon claim-icon-warning" />
) : (
<CheckCircleOutlined className={`claim-large-icon claim-icon-${snapshot.resultVariant}`} />
<CheckCircleOutlined
className={`claim-large-icon claim-icon-${snapshot.resultVariant}`}
/>
)}
<div>
<Typography.Title level={2}>{snapshot.resultTitle}</Typography.Title>
@@ -28,7 +28,10 @@ export function ClaimHeaderCard({
<div className="claim-step-indicator">
<div className="claim-step-dots" aria-label={`当前第 ${currentStep}`}>
{[1, 2, 3, 4].map((step) => (
<span key={step} className={currentStep >= step ? 'claim-step-dot active' : 'claim-step-dot'}>
<span
key={step}
className={currentStep >= step ? 'claim-step-dot active' : 'claim-step-dot'}
>
{step}
</span>
))}
@@ -175,7 +175,12 @@ function ClaimBindingStep({
) : null}
<Space size={12} wrap className="claim-action-row">
<Button size="large" icon={<ReloadOutlined />} loading={refreshingRole} onClick={onRefreshRole}>
<Button
size="large"
icon={<ReloadOutlined />}
loading={refreshingRole}
onClick={onRefreshRole}
>
</Button>
<Tooltip title="角色绑定错误时,请点击「换绑角色」,然后重新扫码绑定正确的角色">
@@ -303,7 +308,9 @@ export function ClaimResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
{snapshot.resultVariant === 'warning' ? (
<ExclamationCircleOutlined className="claim-large-icon claim-icon-warning" />
) : (
<CheckCircleOutlined className={`claim-large-icon claim-icon-${snapshot.resultVariant}`} />
<CheckCircleOutlined
className={`claim-large-icon claim-icon-${snapshot.resultVariant}`}
/>
)}
<div>
<Typography.Title level={2}>{snapshot.resultTitle}</Typography.Title>
@@ -36,9 +36,7 @@ export function ClaimUidStep({
</Button>
</Space>
<p className="claim-uid-warning">
</p>
<p className="claim-uid-warning"></p>
<div className="claim-uid-guide">
<img
@@ -65,14 +65,23 @@ export default function CollectPage() {
>
<Input placeholder="订单号" />
</Form.Item>
<Button type="primary" htmlType="submit" icon={<SearchOutlined />} loading={lookupLoading}>
<Button
type="primary"
htmlType="submit"
icon={<SearchOutlined />}
loading={lookupLoading}
>
</Button>
</Space.Compact>
</Form>
{complete ? (
<Result status="success" title="资料已提交完成" subTitle="订单已进入待分配状态,请等待处理。" />
<Result
status="success"
title="资料已提交完成"
subTitle="订单已进入待分配状态,请等待处理。"
/>
) : null}
{!complete && lookupOrders.length > 1 ? (
@@ -103,7 +112,9 @@ export default function CollectPage() {
key={field.key}
label={field.label}
name={field.key}
rules={field.required ? [{ required: true, message: `请选择/填写${field.label}` }] : []}
rules={
field.required ? [{ required: true, message: `请选择/填写${field.label}` }] : []
}
>
{field.type === 'select' && (field.options || []).length > 0 ? (
<Radio.Group
@@ -125,9 +136,7 @@ export default function CollectPage() {
showPasteHint
captureGlobalPaste
value={
screenshotsByOrder[currentOrder.workOrderId] ??
currentOrder.screenshots ??
[]
screenshotsByOrder[currentOrder.workOrderId] ?? currentOrder.screenshots ?? []
}
onChange={(files) =>
setScreenshotsByOrder((prev) => ({
@@ -171,5 +180,7 @@ export default function CollectPage() {
}
function isCollectOrderNotFoundError(error: unknown) {
return String((error as { errorCode?: string })?.errorCode || '').trim() === 'collect_order_not_found'
return (
String((error as { errorCode?: string })?.errorCode || '').trim() === 'collect_order_not_found'
)
}
@@ -1,4 +1,10 @@
import { ClockCircleOutlined, PushpinOutlined, ReloadOutlined, SearchOutlined, TrophyOutlined } from '@ant-design/icons'
import {
ClockCircleOutlined,
PushpinOutlined,
ReloadOutlined,
SearchOutlined,
TrophyOutlined,
} from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
Alert,
@@ -96,8 +102,7 @@ export default function WorkerHallPage() {
}),
])
const pendingAcceptanceCount =
pendingAcceptanceResponse.data.pagination.total
const pendingAcceptanceCount = pendingAcceptanceResponse.data.pagination.total
const problemCount = problemResponse.data.pagination.total
return {
@@ -124,10 +129,8 @@ export default function WorkerHallPage() {
const pendingAcceptanceCount = hallSummaryQuery.data?.pendingAcceptanceCount || 0
const problemCount = hallSummaryQuery.data?.problemCount || 0
const maxActiveOrders = Number(worker?.level?.permissions.maxActiveOrders || 0)
const remainingSlots =
maxActiveOrders > 0 ? Math.max(0, maxActiveOrders - activeCount) : null
const shouldWarn =
(remainingSlots !== null && remainingSlots <= 0) || problemCount > 0
const remainingSlots = maxActiveOrders > 0 ? Math.max(0, maxActiveOrders - activeCount) : null
const shouldWarn = (remainingSlots !== null && remainingSlots <= 0) || problemCount > 0
async function refreshAll() {
await Promise.all([
@@ -199,14 +202,13 @@ export default function WorkerHallPage() {
{
label: '当前余额',
value: worker ? formatMoney(worker.wallet.availableAmount) : '-',
note: worker ? `冻结押金 ${formatMoney(worker.wallet.frozenDepositAmount)}` : '可用于抢单冻结',
note: worker
? `冻结押金 ${formatMoney(worker.wallet.frozenDepositAmount)}`
: '可用于抢单冻结',
},
{
label: '接单上限',
value:
remainingSlots === null
? '未配置'
: `${activeCount}/${maxActiveOrders}`,
value: remainingSlots === null ? '未配置' : `${activeCount}/${maxActiveOrders}`,
note:
remainingSlots === null
? '当前等级待配置'
@@ -222,11 +224,17 @@ export default function WorkerHallPage() {
<div className="worker-hall-mobile-top-bar">
<div className="worker-hall-mobile-stat-pill">
<span className="worker-hall-mobile-stat-item">
<strong className="worker-hall-mobile-stat-highlight">{worker ? formatMoney(worker.wallet.availableAmount) : '-'}</strong>
{' '}
<strong className="worker-hall-mobile-stat-highlight">
{worker ? formatMoney(worker.wallet.availableAmount) : '-'}
</strong>
</span>
<span className="worker-hall-mobile-stat-divider">|</span>
<span className="worker-hall-mobile-stat-item">
<strong>{remainingSlots === null ? '-' : `${activeCount}/${maxActiveOrders}`}</strong>
{' '}
<strong>
{remainingSlots === null ? '-' : `${activeCount}/${maxActiveOrders}`}
</strong>
</span>
</div>
@@ -242,20 +250,14 @@ export default function WorkerHallPage() {
</Tooltip>
<div className="worker-hall-mobile-autorefresh-switch">
<span className="worker-hall-mobile-autorefresh-text"></span>
<Switch
size="small"
checked={autoRefresh}
onChange={toggleAutoRefresh}
/>
<Switch size="small" checked={autoRefresh} onChange={toggleAutoRefresh} />
</div>
<Button
size="small"
shape="circle"
icon={<ReloadOutlined />}
loading={
ordersQuery.isFetching ||
profileQuery.isFetching ||
hallSummaryQuery.isFetching
ordersQuery.isFetching || profileQuery.isFetching || hallSummaryQuery.isFetching
}
onClick={() => refreshAll()}
/>
@@ -356,11 +358,7 @@ export default function WorkerHallPage() {
/>
) : (
orders.map((order) => {
const disabledReason = resolveGrabDisabledReason(
order,
worker,
remainingSlots,
)
const disabledReason = resolveGrabDisabledReason(order, worker, remainingSlots)
return (
<article key={order.workOrderId} className="worker-hall-mobile-card">
@@ -368,7 +366,11 @@ export default function WorkerHallPage() {
<div className="worker-hall-mobile-card-top">
<div className="worker-hall-mobile-card-tags">
{order.pinnedAt ? (
<Tag color="gold" icon={<PushpinOutlined />} className="worker-hall-mobile-tag">
<Tag
color="gold"
icon={<PushpinOutlined />}
className="worker-hall-mobile-tag"
>
</Tag>
) : null}
@@ -383,7 +385,11 @@ export default function WorkerHallPage() {
</Tag>
) : null}
{Number(order.timeoutMinutes || 0) > 0 ? (
<Tag color="orange" icon={<ClockCircleOutlined />} className="worker-hall-mobile-tag">
<Tag
color="orange"
icon={<ClockCircleOutlined />}
className="worker-hall-mobile-tag"
>
{order.timeoutMinutes}
</Tag>
) : null}
@@ -395,7 +401,10 @@ export default function WorkerHallPage() {
{/* 商品标题与保证金说明 */}
<div className="worker-hall-mobile-card-main">
<strong className="worker-hall-mobile-card-title" title={order.productName || '未命名商品'}>
<strong
className="worker-hall-mobile-card-title"
title={order.productName || '未命名商品'}
>
{order.productName || '未命名商品'}
</strong>
<div className="worker-hall-mobile-card-meta">
@@ -478,11 +487,7 @@ export default function WorkerHallPage() {
>
<div className="worker-hall-autorefresh-box">
<span className="worker-hall-autorefresh-label"></span>
<Switch
size="small"
checked={autoRefresh}
onChange={toggleAutoRefresh}
/>
<Switch size="small" checked={autoRefresh} onChange={toggleAutoRefresh} />
</div>
</Tooltip>
<Button
@@ -606,17 +611,17 @@ export default function WorkerHallPage() {
<>
<div className="worker-hall-card-grid">
{orders.map((order) => {
const disabledReason = resolveGrabDisabledReason(
order,
worker,
remainingSlots,
)
const disabledReason = resolveGrabDisabledReason(order, worker, remainingSlots)
return (
<article key={order.workOrderId} className="worker-hall-card">
<div className="worker-hall-card-title">
{order.pinnedAt ? (
<Tag color="gold" icon={<PushpinOutlined />} className="worker-hall-pinned-tag">
<Tag
color="gold"
icon={<PushpinOutlined />}
className="worker-hall-pinned-tag"
>
</Tag>
) : null}
@@ -640,8 +645,7 @@ export default function WorkerHallPage() {
<Typography.Text type="secondary">
{formatMoney(order.sharing.unitReward)} · {' '}
{order.sharingProgress?.joinedQuantity || 0}/
{order.sharing.totalQuantity} · {' '}
{resolveSharingRemaining(order)}
{order.sharing.totalQuantity} · {resolveSharingRemaining(order)}
</Typography.Text>
</div>
) : null}
@@ -658,19 +662,13 @@ export default function WorkerHallPage() {
<div className="worker-hall-card-amount">
{formatMoney(order.rewardAmount)}
</div>
<Typography.Text
type="secondary"
className="worker-hall-card-deposit"
>
<Typography.Text type="secondary" className="worker-hall-card-deposit">
{formatMoney(order.freezeDepositAmount)}
</Typography.Text>
</div>
{disabledReason ? (
<Typography.Text
type="danger"
className="worker-hall-card-hint"
>
<Typography.Text type="danger" className="worker-hall-card-hint">
{disabledReason}
</Typography.Text>
) : null}
@@ -141,7 +141,9 @@ export default function WorkerLoginPage() {
))}
</div>
<div className="auth-brand-footer">© {new Date().getFullYear()} · </div>
<div className="auth-brand-footer">
© {new Date().getFullYear()} ·
</div>
</aside>
{/* 登录注册表单区 */}
@@ -80,7 +80,9 @@ const PRESET_NOTE_TAGS: Array<{ label: string; theme: NoteTheme }> = [
]
function resolveNoteTheme(note?: string): NoteTheme {
const text = String(note || '').trim().toLowerCase()
const text = String(note || '')
.trim()
.toLowerCase()
if (!text) return 'default'
// 1. 危险 / 异常 / 问题类 (Red)
@@ -352,7 +354,11 @@ export default function WorkerOrdersPage() {
{row.categoryName ? <Tag color="blue">{row.categoryName}</Tag> : null}
{row.myShare ? <Tag color="purple"></Tag> : null}
</Space>
<Typography.Text type="secondary" style={{ fontSize: 12 }} ellipsis={{ tooltip: getDisplayOrderNo(row) }}>
<Typography.Text
type="secondary"
style={{ fontSize: 12 }}
ellipsis={{ tooltip: getDisplayOrderNo(row) }}
>
{getDisplayOrderNo(row)}
</Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
@@ -373,7 +379,11 @@ export default function WorkerOrdersPage() {
) : null}
</Space>
{row.status === 'problem' && String(row.problemNote || '').trim() ? (
<Typography.Text type="danger" ellipsis={{ tooltip: row.problemNote }} style={{ fontSize: 12 }}>
<Typography.Text
type="danger"
ellipsis={{ tooltip: row.problemNote }}
style={{ fontSize: 12 }}
>
{row.problemNote}
</Typography.Text>
) : null}
@@ -410,7 +420,11 @@ export default function WorkerOrdersPage() {
const draftCount = draftFiles.length
if (!hasAcceptance && !submittedAt && draftCount === 0) {
return <Typography.Text type="secondary" style={{ fontSize: 12 }}></Typography.Text>
return (
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
</Typography.Text>
)
}
return (
<div className="cell-stack">
@@ -431,7 +445,11 @@ export default function WorkerOrdersPage() {
</div>
) : null}
{getWorkerAcceptance(row)?.note ? (
<Typography.Text type="secondary" ellipsis={{ tooltip: getWorkerAcceptance(row)?.note }} style={{ fontSize: 12 }}>
<Typography.Text
type="secondary"
ellipsis={{ tooltip: getWorkerAcceptance(row)?.note }}
style={{ fontSize: 12 }}
>
{getWorkerAcceptance(row)?.note}
</Typography.Text>
) : null}
@@ -443,16 +461,18 @@ export default function WorkerOrdersPage() {
title: '快捷复制',
width: 250,
render: (_, row) => {
const items = getMaterialDetailItems(row).filter((item) =>
String(item.value || '').trim(),
)
const items = getMaterialDetailItems(row).filter((item) => String(item.value || '').trim())
const canCopyInfo = getCopyOrderInfoItems(row).length > 0
const screenshots = getMaterialScreenshots(row)
return (
<div className="cell-stack">
{items.length > 0 ? (
items.map((item) => (
<Typography.Text key={item.key} style={{ fontSize: 12 }} ellipsis={{ tooltip: `${item.label}: ${item.value}` }}>
<Typography.Text
key={item.key}
style={{ fontSize: 12 }}
ellipsis={{ tooltip: `${item.label}: ${item.value}` }}
>
{item.label}: {item.value}
</Typography.Text>
))
@@ -461,9 +481,7 @@ export default function WorkerOrdersPage() {
</Typography.Text>
)}
{screenshots.length > 0 ? (
<ImagePreviewList files={screenshots} size={34} />
) : null}
{screenshots.length > 0 ? <ImagePreviewList files={screenshots} size={34} /> : null}
<Space wrap size={4}>
<Button
size="small"
@@ -625,11 +643,7 @@ export default function WorkerOrdersPage() {
/>
) : orders.length === 0 ? (
<Empty
description={
keyword
? `没有找到与 “${keyword}” 相关的订单`
: '当前筛选下暂无订单'
}
description={keyword ? `没有找到与 “${keyword}” 相关的订单` : '当前筛选下暂无订单'}
/>
) : (
orders.map((order) => {
@@ -695,7 +709,8 @@ export default function WorkerOrdersPage() {
</span>
{order.myShare ? (
<span className="worker-order-mobile-sub-item">
{order.myShare.quantity} ({formatMoney(order.myShare.shareReward)})
{order.myShare.quantity} ({formatMoney(order.myShare.shareReward)}
)
</span>
) : null}
<span className="worker-order-mobile-sub-time">
@@ -718,8 +733,12 @@ export default function WorkerOrdersPage() {
<div className="worker-order-mobile-credentials-grid">
{items.map((item) => (
<div key={item.key} className="worker-order-mobile-credential-line">
<span className="worker-order-mobile-credential-label">{item.label}:</span>
<span className="worker-order-mobile-credential-val">{item.value}</span>
<span className="worker-order-mobile-credential-label">
{item.label}:
</span>
<span className="worker-order-mobile-credential-val">
{item.value}
</span>
</div>
))}
</div>
@@ -755,7 +774,9 @@ export default function WorkerOrdersPage() {
)}
{/* 验收凭据与暂存状态 */}
{(hasAcceptance || (draftCount > 0 && !hasSubmittedAcceptance(order)) || noteText) && (
{(hasAcceptance ||
(draftCount > 0 && !hasSubmittedAcceptance(order)) ||
noteText) && (
<div className="worker-order-mobile-acceptance-strip">
{hasAcceptance ? (
<div className="worker-order-mobile-acceptance-files">
@@ -872,9 +893,7 @@ export default function WorkerOrdersPage() {
label: (
<span>
{item.label}
{count > 0 ? (
<span className="worker-order-tab-count">{count}</span>
) : null}
{count > 0 ? <span className="worker-order-tab-count">{count}</span> : null}
</span>
),
}
@@ -929,9 +948,7 @@ export default function WorkerOrdersPage() {
) : (
<Empty
description={
keyword
? `没有找到与 “${keyword}” 相关的订单`
: '当前筛选下暂无订单'
keyword ? `没有找到与 “${keyword}” 相关的订单` : '当前筛选下暂无订单'
}
/>
),
@@ -988,9 +1005,7 @@ export default function WorkerOrdersPage() {
<div className="worker-order-detail-stack">
<Card title="基础信息" size="small">
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label="商品">
{detailOrder.productName || '-'}
</Descriptions.Item>
<Descriptions.Item label="商品">{detailOrder.productName || '-'}</Descriptions.Item>
<Descriptions.Item label="分类">
{detailOrder.categoryName || '-'}
</Descriptions.Item>
@@ -1027,10 +1042,7 @@ export default function WorkerOrdersPage() {
</Card>
<Card title="资料信息" size="small">
{renderDetailFieldValues(
getMaterialDetailItems(detailOrder),
'当前还没有资料内容',
)}
{renderDetailFieldValues(getMaterialDetailItems(detailOrder), '当前还没有资料内容')}
{getMaterialScreenshots(detailOrder).length > 0 ? (
<div style={{ marginTop: 12 }}>
<Typography.Text type="secondary"></Typography.Text>
@@ -1174,10 +1186,7 @@ export default function WorkerOrdersPage() {
>
<Form form={form} layout="vertical" onFinish={submitAcceptance}>
<Form.Item label="完成说明" name="note">
<Input.TextArea
rows={3}
placeholder="可补充代练结果、注意事项或截图说明。"
/>
<Input.TextArea rows={3} placeholder="可补充代练结果、注意事项或截图说明。" />
</Form.Item>
<Form.Item label="验收图片" required>
<ImageUpload
@@ -1224,9 +1233,7 @@ function getDisplayOrderNo(order: Pick<WorkOrder, 'platformOrderId'> | null | un
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)
@@ -1261,9 +1268,7 @@ async function toPngBlob(source: Blob): Promise<Blob> {
const context = canvas.getContext('2d')
if (!context) return source
context.drawImage(image, 0, 0)
const png = await new Promise<Blob | null>((resolve) =>
canvas.toBlob(resolve, 'image/png'),
)
const png = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/png'))
return png || source
} finally {
URL.revokeObjectURL(url)
@@ -1332,10 +1337,7 @@ function getCopyOrderInfoItems(order: WorkOrder): Array<Pick<DetailFieldItem, 'l
String(item.value || '').trim(),
)
return [
...(productName ? [{ label: '物品', value: productName }] : []),
...materialItems,
]
return [...(productName ? [{ label: '物品', value: productName }] : []), ...materialItems]
}
function renderDetailFieldValues(items: DetailFieldItem[], emptyText: string) {
@@ -1432,9 +1434,7 @@ function getDraftImageUrls(row: WorkOrder): string[] {
function getAcceptanceSubmittedAt(order: WorkOrder): string | null {
if (order.myShare) return order.myShare.submittedAt
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
}
@@ -138,13 +138,7 @@ export default function WorkerProfilePage() {
})
const requestsQuery = useQuery({
queryKey: [
'worker-finance-requests',
requestType,
requestStatus,
requestPage,
requestPageSize,
],
queryKey: ['worker-finance-requests', requestType, requestStatus, requestPage, requestPageSize],
queryFn: () =>
fetchWorkerFinanceRequests({
page: requestPage,
@@ -275,9 +269,7 @@ export default function WorkerProfilePage() {
title: '类型',
width: 150,
render: (_, row) => (
<Tag color={resolveLedgerTagColor(row.ledgerType)}>
{formatLedgerType(row.ledgerType)}
</Tag>
<Tag color={resolveLedgerTagColor(row.ledgerType)}>{formatLedgerType(row.ledgerType)}</Tag>
),
},
{
@@ -310,9 +302,7 @@ export default function WorkerProfilePage() {
<div className="cell-stack">
<Typography.Text>{row.note || '-'}</Typography.Text>
{row.relatedWorkOrderId ? (
<Typography.Text type="secondary">
#{row.relatedWorkOrderId}
</Typography.Text>
<Typography.Text type="secondary"> #{row.relatedWorkOrderId}</Typography.Text>
) : null}
</div>
),
@@ -349,9 +339,7 @@ export default function WorkerProfilePage() {
<Typography.Text>
{formatWithdrawChannel(row.accountChannel)} / {row.accountName || '-'}
</Typography.Text>
<Typography.Text type="secondary">
{maskAccountNo(row.accountNo)}
</Typography.Text>
<Typography.Text type="secondary">{maskAccountNo(row.accountNo)}</Typography.Text>
</div>
) : (
renderRechargeRequestSummary(row)
@@ -361,9 +349,7 @@ export default function WorkerProfilePage() {
title: '状态',
width: 130,
render: (_, row) => (
<Tag color={resolveRequestStatusColor(row.status)}>
{formatRequestStatus(row.status)}
</Tag>
<Tag color={resolveRequestStatusColor(row.status)}>{formatRequestStatus(row.status)}</Tag>
),
},
{
@@ -373,9 +359,7 @@ export default function WorkerProfilePage() {
<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>
),
@@ -415,10 +399,7 @@ export default function WorkerProfilePage() {
{/* 沉浸式顶部个人卡片 */}
<div className="worker-profile-mobile-hero">
<div className="worker-profile-mobile-user-row">
<Avatar
size={52}
className="worker-profile-mobile-avatar"
>
<Avatar size={52} className="worker-profile-mobile-avatar">
{resolveWorkerInitial(worker)}
</Avatar>
<div className="worker-profile-mobile-user-info">
@@ -431,7 +412,10 @@ export default function WorkerProfilePage() {
<span className="worker-profile-mobile-vip-badge">
{worker.level?.name || '未分级'}
</span>
<Tag color={resolveWorkerStatusColor(worker.status)} className="worker-profile-mobile-tag">
<Tag
color={resolveWorkerStatusColor(worker.status)}
className="worker-profile-mobile-tag"
>
{formatWorkerStatus(worker.status)}
</Tag>
<Tag className="worker-profile-mobile-tag">
@@ -447,7 +431,9 @@ export default function WorkerProfilePage() {
shape="circle"
icon={<ReloadOutlined />}
className="worker-profile-mobile-refresh-btn"
loading={profileQuery.isFetching || ledgersQuery.isFetching || requestsQuery.isFetching}
loading={
profileQuery.isFetching || ledgersQuery.isFetching || requestsQuery.isFetching
}
onClick={() => refreshAll()}
/>
</div>
@@ -456,7 +442,9 @@ export default function WorkerProfilePage() {
<div className="worker-profile-mobile-hero-bottom">
<div className="worker-profile-mobile-invite-bar">
<span className="worker-profile-mobile-invite-label">:</span>
<strong className="worker-profile-mobile-invite-code">{worker.inviteCode || '-'}</strong>
<strong className="worker-profile-mobile-invite-code">
{worker.inviteCode || '-'}
</strong>
{worker.inviteCode ? (
<button
type="button"
@@ -471,7 +459,12 @@ export default function WorkerProfilePage() {
{levelProgress && levelProgress.nextThreshold !== null ? (
<div className="worker-profile-mobile-level-box">
<div className="worker-profile-mobile-level-text">
{summary?.acceptedOrderCount || 0} · {Math.max(0, levelProgress.nextThreshold - (summary?.acceptedOrderCount || 0))}
{summary?.acceptedOrderCount || 0} · {' '}
{Math.max(
0,
levelProgress.nextThreshold - (summary?.acceptedOrderCount || 0),
)}{' '}
</div>
<Progress
percent={levelProgress.progressPercent}
@@ -557,7 +550,10 @@ export default function WorkerProfilePage() {
className="worker-profile-mobile-tool-item"
onClick={() => setPasswordOpen(true)}
>
<div className="worker-profile-mobile-tool-icon" style={{ background: '#eff6ff', color: '#3b82f6' }}>
<div
className="worker-profile-mobile-tool-icon"
style={{ background: '#eff6ff', color: '#3b82f6' }}
>
<LockOutlined />
</div>
<span></span>
@@ -567,7 +563,10 @@ export default function WorkerProfilePage() {
className="worker-profile-mobile-tool-item"
onClick={() => setContactOpen(true)}
>
<div className="worker-profile-mobile-tool-icon" style={{ background: '#f0fdf4', color: '#16a34a' }}>
<div
className="worker-profile-mobile-tool-icon"
style={{ background: '#f0fdf4', color: '#16a34a' }}
>
<CustomerServiceOutlined />
</div>
<span></span>
@@ -578,7 +577,10 @@ export default function WorkerProfilePage() {
disabled={!worker.inviteCode}
onClick={copyInviteCode}
>
<div className="worker-profile-mobile-tool-icon" style={{ background: '#fef3c7', color: '#d97706' }}>
<div
className="worker-profile-mobile-tool-icon"
style={{ background: '#fef3c7', color: '#d97706' }}
>
<CopyOutlined />
</div>
<span></span>
@@ -588,7 +590,10 @@ export default function WorkerProfilePage() {
className="worker-profile-mobile-tool-item"
onClick={submitLogout}
>
<div className="worker-profile-mobile-tool-icon" style={{ background: '#fef2f2', color: '#ef4444' }}>
<div
className="worker-profile-mobile-tool-icon"
style={{ background: '#fef2f2', color: '#ef4444' }}
>
<LogoutOutlined />
</div>
<span>退</span>
@@ -657,7 +662,9 @@ export default function WorkerProfilePage() {
<div className="worker-profile-mobile-records">
{ledgersQuery.isLoading ? (
<div className="worker-hall-loading"><Spin size="large" /></div>
<div className="worker-hall-loading">
<Spin size="large" />
</div>
) : (ledgersQuery.data?.data.items || []).length === 0 ? (
<Empty description="暂无钱包流水" />
) : (
@@ -702,7 +709,9 @@ export default function WorkerProfilePage() {
<div className="worker-orders-mobile-pagination">
<Pagination
current={ledgersQuery.data?.data.pagination.page || ledgerPage}
pageSize={ledgersQuery.data?.data.pagination.pageSize || ledgerPageSize}
pageSize={
ledgersQuery.data?.data.pagination.pageSize || ledgerPageSize
}
total={ledgersQuery.data?.data.pagination.total || 0}
simple
onChange={(nextPage: number, nextPageSize: number) => {
@@ -760,7 +769,9 @@ export default function WorkerProfilePage() {
<div className="worker-profile-mobile-records">
{requestsQuery.isLoading ? (
<div className="worker-hall-loading"><Spin size="large" /></div>
<div className="worker-hall-loading">
<Spin size="large" />
</div>
) : (requestsQuery.data?.data.items || []).length === 0 ? (
<Empty description="暂无申请记录" />
) : (
@@ -779,7 +790,8 @@ export default function WorkerProfilePage() {
</div>
{row.requestType === 'withdraw' ? (
<div className="worker-profile-record-row-meta">
: {formatWithdrawChannel(row.accountChannel)} / {row.accountName || '-'} ({maskAccountNo(row.accountNo)})
: {formatWithdrawChannel(row.accountChannel)} /{' '}
{row.accountName || '-'} ({maskAccountNo(row.accountNo)})
</div>
) : (
<div className="worker-profile-record-row-meta">
@@ -790,7 +802,10 @@ export default function WorkerProfilePage() {
<div className="worker-profile-record-note">: {row.note}</div>
) : null}
{row.reviewedNote ? (
<div className="worker-profile-record-note" style={{ color: '#d97706' }}>
<div
className="worker-profile-record-note"
style={{ color: '#d97706' }}
>
: {row.reviewedNote}
</div>
) : null}
@@ -805,7 +820,9 @@ export default function WorkerProfilePage() {
<div className="worker-orders-mobile-pagination">
<Pagination
current={requestsQuery.data?.data.pagination.page || requestPage}
pageSize={requestsQuery.data?.data.pagination.pageSize || requestPageSize}
pageSize={
requestsQuery.data?.data.pagination.pageSize || requestPageSize
}
total={requestsQuery.data?.data.pagination.total || 0}
simple
onChange={(nextPage: number, nextPageSize: number) => {
@@ -828,11 +845,15 @@ export default function WorkerProfilePage() {
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">{worker.displayName || '-'}</span>
<span className="worker-profile-mobile-acc-val">
{worker.displayName || '-'}
</span>
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">{worker.phone || '-'}</span>
<span className="worker-profile-mobile-acc-val">
{worker.phone || '-'}
</span>
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
@@ -844,7 +865,9 @@ export default function WorkerProfilePage() {
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">{worker.level?.name || '-'}</span>
<span className="worker-profile-mobile-acc-val">
{worker.level?.name || '-'}
</span>
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
@@ -861,12 +884,16 @@ export default function WorkerProfilePage() {
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">
{worker.inviter ? worker.inviter.displayName || worker.inviter.username : '-'}
{worker.inviter
? worker.inviter.displayName || worker.inviter.username
: '-'}
</span>
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">{summary?.acceptedOrderCount || 0} </span>
<span className="worker-profile-mobile-acc-val">
{summary?.acceptedOrderCount || 0}
</span>
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
@@ -876,7 +903,9 @@ export default function WorkerProfilePage() {
</div>
<div className="worker-profile-mobile-account-item">
<span className="worker-profile-mobile-acc-label"></span>
<span className="worker-profile-mobile-acc-val">{formatDateTime(worker.createdAt)}</span>
<span className="worker-profile-mobile-acc-val">
{formatDateTime(worker.createdAt)}
</span>
</div>
</div>
</div>
@@ -915,16 +944,12 @@ export default function WorkerProfilePage() {
<Card size="small">
<Space size={16} wrap align="start">
<Avatar size={64}>
{resolveWorkerInitial(worker)}
</Avatar>
<Avatar size={64}>{resolveWorkerInitial(worker)}</Avatar>
<Space direction="vertical" size={2}>
<Typography.Title level={4} style={{ margin: 0 }}>
{worker.displayName || worker.username}
</Typography.Title>
<Typography.Text type="secondary">
{worker.username}
</Typography.Text>
<Typography.Text type="secondary">{worker.username}</Typography.Text>
<Typography.Text type="secondary">
{worker.phone || '-'}
</Typography.Text>
@@ -941,9 +966,7 @@ export default function WorkerProfilePage() {
</Space>
<Space direction="vertical" size={4} style={{ marginTop: 12, width: '100%' }}>
<Space wrap size={[8, 8]}>
<Typography.Text type="secondary">
</Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
<Typography.Text strong>{worker.inviteCode || '-'}</Typography.Text>
<Button
size="small"
@@ -958,8 +981,12 @@ export default function WorkerProfilePage() {
{levelProgress && levelProgress.nextThreshold !== null ? (
<Space direction="vertical" size={0} style={{ width: '100%' }}>
<Typography.Text type="secondary">
{summary?.acceptedOrderCount || 0}
{Math.max(0, levelProgress.nextThreshold - (summary?.acceptedOrderCount || 0))}
{summary?.acceptedOrderCount || 0} {' '}
{Math.max(
0,
levelProgress.nextThreshold - (summary?.acceptedOrderCount || 0),
)}{' '}
</Typography.Text>
<Progress
percent={levelProgress.progressPercent}
@@ -989,7 +1016,9 @@ export default function WorkerProfilePage() {
<strong className="worker-profile-wallet-value">
{formatMoney(worker.wallet.frozenDepositAmount)}
</strong>
<small className="worker-profile-wallet-note"></small>
<small className="worker-profile-wallet-note">
</small>
</div>
<div className="worker-profile-wallet-item">
<span className="worker-profile-wallet-label"></span>
@@ -1029,10 +1058,7 @@ export default function WorkerProfilePage() {
<Button icon={<LockOutlined />} onClick={() => setPasswordOpen(true)}>
</Button>
<Button
icon={<CustomerServiceOutlined />}
onClick={() => setContactOpen(true)}
>
<Button icon={<CustomerServiceOutlined />} onClick={() => setContactOpen(true)}>
</Button>
<Button danger icon={<LogoutOutlined />} onClick={submitLogout}>
@@ -1080,7 +1106,9 @@ export default function WorkerProfilePage() {
</Space>
</Descriptions.Item>
<Descriptions.Item label="邀请人">
{worker.inviter ? worker.inviter.displayName || worker.inviter.username : '-'}
{worker.inviter
? worker.inviter.displayName || worker.inviter.username
: '-'}
</Descriptions.Item>
<Descriptions.Item label="已累计完成">
{summary?.acceptedOrderCount || 0}
@@ -1240,7 +1268,8 @@ export default function WorkerProfilePage() {
}}
pagination={{
current: requestsQuery.data?.data.pagination.page || requestPage,
pageSize: requestsQuery.data?.data.pagination.pageSize || requestPageSize,
pageSize:
requestsQuery.data?.data.pagination.pageSize || requestPageSize,
total: requestsQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50],
@@ -1313,9 +1342,7 @@ export default function WorkerProfilePage() {
</Descriptions.Item>
<Descriptions.Item label="收款账号">
{financeConfig?.recharge.accountNo ? (
<Typography.Text copyable>
{financeConfig.recharge.accountNo}
</Typography.Text>
<Typography.Text copyable>{financeConfig.recharge.accountNo}</Typography.Text>
) : (
'-'
)}
@@ -1434,10 +1461,7 @@ export default function WorkerProfilePage() {
<Input placeholder="请输入支付宝账号、微信号或银行卡号" />
</Form.Item>
<Form.Item label="备注说明" name="note">
<Input.TextArea
rows={4}
placeholder="可补充到账要求、手机号、开户行等说明。"
/>
<Input.TextArea rows={4} placeholder="可补充到账要求、手机号、开户行等说明。" />
</Form.Item>
</Form>
</Modal>
@@ -1531,9 +1555,7 @@ export default function WorkerProfilePage() {
<Descriptions.Item label="账号">
<Typography.Text copyable>{worker?.username || '-'}</Typography.Text>
</Descriptions.Item>
<Descriptions.Item label="昵称">
{worker?.displayName || '-'}
</Descriptions.Item>
<Descriptions.Item label="昵称">{worker?.displayName || '-'}</Descriptions.Item>
<Descriptions.Item label="手机号">
<Typography.Text copyable>{worker?.phone || '-'}</Typography.Text>
</Descriptions.Item>
@@ -1559,8 +1581,7 @@ function resolveAvailableForWithdraw(
) {
return Math.max(
0,
Number(worker?.wallet.availableAmount || 0) -
Number(summary?.pendingWithdrawAmount || 0),
Number(worker?.wallet.availableAmount || 0) - Number(summary?.pendingWithdrawAmount || 0),
)
}
@@ -1634,14 +1655,16 @@ function formatRequestStatus(status: string) {
}
function hasRechargeConfig(
financeConfig: {
recharge?: { accountName?: string; accountNo?: string; qrCodeImage?: unknown }
} | undefined,
financeConfig:
| {
recharge?: { accountName?: string; accountNo?: string; qrCodeImage?: unknown }
}
| undefined,
) {
return Boolean(
financeConfig?.recharge?.accountName ||
financeConfig?.recharge?.accountNo ||
financeConfig?.recharge?.qrCodeImage,
financeConfig?.recharge?.accountNo ||
financeConfig?.recharge?.qrCodeImage,
)
}
@@ -5,13 +5,15 @@ export function fetchAdminDashboardSummary() {
return apiGet<AdminDashboardSummary>('/api/v1/admin/dashboard/summary')
}
export function fetchAdminLoginLogs(params: {
page?: number
pageSize?: number
username?: string
dateFrom?: string
dateTo?: string
} = {}) {
export function fetchAdminLoginLogs(
params: {
page?: number
pageSize?: number
username?: string
dateFrom?: string
dateTo?: string
} = {},
) {
const search = new URLSearchParams()
if (params.page) search.set('page', String(params.page))
if (params.pageSize) search.set('pageSize', String(params.pageSize))
@@ -23,9 +23,7 @@ export function fetchAdminKuaishouIndustryShops() {
return apiGet<AdminKuaishouIndustryShopListResult>('/api/v1/admin/kuaishou-industry/shops')
}
export function listAdminKuaishouIndustryRefunds(
payload: AdminKuaishouIndustryRefundListPayload,
) {
export function listAdminKuaishouIndustryRefunds(payload: AdminKuaishouIndustryRefundListPayload) {
return apiPost<AdminKuaishouIndustryOpenApiResult>(
'/api/v1/admin/kuaishou-industry/refunds/list',
payload,
@@ -8,9 +8,7 @@ import type {
} from '@/types/admin'
export function fetchAdminAffiliateDashConfig() {
return apiGet<AdminAffiliateDashConfigResponse>(
'/api/v1/admin/platform-config/affiliate-dash',
)
return apiGet<AdminAffiliateDashConfigResponse>('/api/v1/admin/platform-config/affiliate-dash')
}
export function saveAdminAffiliateDashConfig(payload: AdminAffiliateDashConfig) {
@@ -27,10 +25,12 @@ export function matchAdminAffiliateDashSku(productNo: string) {
)
}
export function fetchAdminAffiliateDashProducts(payload: {
page?: number
size?: number
} = {}) {
export function fetchAdminAffiliateDashProducts(
payload: {
page?: number
size?: number
} = {},
) {
return apiPost<AdminAffiliateDashProductListResult>(
'/api/v1/admin/platform-config/affiliate-dash/products',
payload,
@@ -41,23 +41,21 @@ export function fetchAdminCloudtentaclesOverrideRules() {
)
}
export function saveAdminCloudtentaclesOverrideRules(
payload: {
enabled: boolean
rules: Array<{
id?: string
enabled?: boolean
productName?: string
sourceKey?: string
deliveryItems?: Array<{
cloudSkuId?: number
cloudSkuName?: string
quantity?: number
}>
notes?: string
export function saveAdminCloudtentaclesOverrideRules(payload: {
enabled: boolean
rules: Array<{
id?: string
enabled?: boolean
productName?: string
sourceKey?: string
deliveryItems?: Array<{
cloudSkuId?: number
cloudSkuName?: string
quantity?: number
}>
},
) {
notes?: string
}>
}) {
return apiPost<AdminCloudtentaclesOverrideRuleConfig>(
'/api/v1/admin/platform-config/cloudtentacles/override-rules',
payload,
@@ -5,9 +5,7 @@ import type {
} from '@/types/admin'
export function fetchAdminFulfillmentRoutingConfig() {
return apiGet<AdminFulfillmentRoutingConfig>(
'/api/v1/admin/platform-config/fulfillment-routing',
)
return apiGet<AdminFulfillmentRoutingConfig>('/api/v1/admin/platform-config/fulfillment-routing')
}
export function saveAdminFulfillmentRoutingConfig(payload: Partial<AdminFulfillmentRoutingConfig>) {
@@ -9,9 +9,7 @@ import type {
} from '@/types/admin'
export function fetchAdminKuaishouFeifeiConfig() {
return apiGet<AdminKuaishouFeifeiConfigResponse>(
'/api/v1/admin/platform-config/kuaishou-feifei',
)
return apiGet<AdminKuaishouFeifeiConfigResponse>('/api/v1/admin/platform-config/kuaishou-feifei')
}
export function saveAdminKuaishouFeifeiConfig(payload: AdminKuaishouFeifeiConfig) {
@@ -40,10 +38,12 @@ export function fetchAdminKuaishouFeifeiProducts(payload: {
)
}
export function syncAdminKuaishouFeifeiProducts(payload: {
status?: string
supplyProductName?: string
} = {}) {
export function syncAdminKuaishouFeifeiProducts(
payload: {
status?: string
supplyProductName?: string
} = {},
) {
return apiPost<AdminKuaishouFeifeiProductSyncResult>(
'/api/v1/admin/platform-config/kuaishou-feifei/sync-products',
payload,
@@ -13,18 +13,14 @@ export function fetchAdminKuaishouIndustrySourceConfig() {
)
}
export function saveAdminKuaishouIndustrySourceConfig(
payload: AdminKuaishouIndustrySourceConfig,
) {
export function saveAdminKuaishouIndustrySourceConfig(payload: AdminKuaishouIndustrySourceConfig) {
return apiPost<AdminKuaishouIndustryConfigResponse>(
'/api/v1/admin/platform-config/kuaishou-industry-source',
payload,
)
}
export function refreshAdminKuaishouIndustryAccessToken(
payload: { sellerId?: string } = {},
) {
export function refreshAdminKuaishouIndustryAccessToken(payload: { sellerId?: string } = {}) {
return apiPost<AdminKuaishouIndustryRefreshTokenResponse>(
'/api/v1/admin/platform-config/kuaishou-industry-source/refresh-token',
payload,
@@ -32,15 +32,11 @@ export function fetchAdminWorkerPlatformSummary() {
}
export function fetchAdminWorkerLevels() {
return apiGet<{ items: WorkerLevel[] }>(
'/api/v1/admin/worker-platform/levels',
)
return apiGet<{ items: WorkerLevel[] }>('/api/v1/admin/worker-platform/levels')
}
export function fetchAdminWorkCategories() {
return apiGet<{ items: WorkCategory[] }>(
'/api/v1/admin/worker-platform/categories',
)
return apiGet<{ items: WorkCategory[] }>('/api/v1/admin/worker-platform/categories')
}
export function saveAdminWorkCategory(payload: {
@@ -49,22 +45,15 @@ export function saveAdminWorkCategory(payload: {
sortOrder?: number
status?: string
}) {
return apiPost<{ category: WorkCategory }>(
'/api/v1/admin/worker-platform/categories',
payload,
)
return apiPost<{ category: WorkCategory }>('/api/v1/admin/worker-platform/categories', payload)
}
export function deleteAdminWorkCategory(categoryId: number) {
return apiDelete<{ deleted: boolean }>(
`/api/v1/admin/worker-platform/categories/${categoryId}`,
)
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/worker-platform/categories/${categoryId}`)
}
export function deleteAdminWorkerLevel(levelId: number) {
return apiDelete<{ deleted: boolean }>(
`/api/v1/admin/worker-platform/levels/${levelId}`,
)
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/worker-platform/levels/${levelId}`)
}
export function saveAdminWorkerLevel(payload: {
@@ -76,24 +65,15 @@ export function saveAdminWorkerLevel(payload: {
visibleDelaySeconds?: number
status?: string
}) {
return apiPost<{ level: WorkerLevel }>(
'/api/v1/admin/worker-platform/levels',
payload,
)
return apiPost<{ level: WorkerLevel }>('/api/v1/admin/worker-platform/levels', payload)
}
export function fetchAdminWorkerUsers(params?: Record<string, unknown>) {
return apiGet<WorkerListResponse<WorkerUser>>(
'/api/v1/admin/worker-platform/workers',
params,
)
return apiGet<WorkerListResponse<WorkerUser>>('/api/v1/admin/worker-platform/workers', params)
}
export function fetchAdminWorkProductRules(params?: Record<string, unknown>) {
return apiGet<{ items: WorkProductRule[] }>(
'/api/v1/admin/worker-platform/product-rules',
params,
)
return apiGet<{ items: WorkProductRule[] }>('/api/v1/admin/worker-platform/product-rules', params)
}
export function saveAdminWorkProductRule(payload: {
@@ -121,16 +101,11 @@ export function saveAdminWorkProductRule(payload: {
fieldsText?: string
sortOrder?: number
}) {
return apiPost<{ rule: WorkProductRule }>(
'/api/v1/admin/worker-platform/product-rules',
payload,
)
return apiPost<{ rule: WorkProductRule }>('/api/v1/admin/worker-platform/product-rules', payload)
}
export function deleteAdminWorkProductRule(ruleId: number) {
return apiDelete<{ deleted: boolean }>(
`/api/v1/admin/worker-platform/product-rules/${ruleId}`,
)
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/worker-platform/product-rules/${ruleId}`)
}
export function reviewAdminWorkerUser(
@@ -143,17 +118,11 @@ export function reviewAdminWorkerUser(
)
}
export function assignAdminWorkOrderToWorker(
workOrderId: number,
payload: { workerId: number },
) {
export function assignAdminWorkOrderToWorker(workOrderId: number, payload: { workerId: number }) {
return apiPost<{
order: WorkOrder
voucherConsume: WorkOrderVoucherConsumeResult
}>(
`/api/v1/admin/worker-platform/orders/${workOrderId}/assign`,
payload,
)
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/assign`, payload)
}
export function unassignAdminWorkOrder(workOrderId: number) {
@@ -228,10 +197,7 @@ export function reviewAdminWorkerFinanceRequest(
}
export function fetchAdminWorkOrders(params?: Record<string, unknown>) {
return apiGet<WorkerListResponse<WorkOrder>>(
'/api/v1/admin/worker-platform/orders',
params,
)
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/admin/worker-platform/orders', params)
}
export function createAdminMockWorkOrder(payload: {
@@ -241,10 +207,7 @@ export function createAdminMockWorkOrder(payload: {
paymentAmount?: number
materialComplete?: boolean
}) {
return apiPost<{ order: WorkOrder }>(
'/api/v1/admin/worker-platform/orders/mock',
payload,
)
return apiPost<{ order: WorkOrder }>('/api/v1/admin/worker-platform/orders/mock', payload)
}
export function submitAdminWorkOrderMaterial(
@@ -263,9 +226,7 @@ export function publishAdminWorkOrder(workOrderId: number) {
return apiPost<{
order: WorkOrder
voucherConsume: WorkOrderVoucherConsumeResult
}>(
`/api/v1/admin/worker-platform/orders/${workOrderId}/publish`,
)
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/publish`)
}
export function updateAdminWorkOrder(
@@ -288,9 +249,7 @@ export function updateAdminWorkOrder(
}
export function deleteAdminWorkOrder(workOrderId: number) {
return apiDelete<{ deleted: boolean }>(
`/api/v1/admin/worker-platform/orders/${workOrderId}`,
)
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/worker-platform/orders/${workOrderId}`)
}
export function unpublishAdminWorkOrder(workOrderId: number) {
@@ -300,10 +259,9 @@ export function unpublishAdminWorkOrder(workOrderId: number) {
}
export function pinAdminWorkOrder(workOrderId: number, pinned: boolean) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/admin/worker-platform/orders/${workOrderId}/pin`,
{ pinned },
)
return apiPost<{ order: WorkOrder }>(`/api/v1/admin/worker-platform/orders/${workOrderId}/pin`, {
pinned,
})
}
export function fetchAdminWorkOrderSharing(workOrderId: number) {
@@ -348,16 +306,13 @@ export function resolveAdminProblemWorkOrder(
}
export function acceptAdminWorkOrder(workOrderId: number) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/admin/worker-platform/orders/${workOrderId}/accept`,
)
return apiPost<{ order: WorkOrder }>(`/api/v1/admin/worker-platform/orders/${workOrderId}/accept`)
}
export function acceptAdminWorkOrders(workOrderIds: number[]) {
return apiPost<{ orders: WorkOrder[] }>(
'/api/v1/admin/worker-platform/orders/accept-batch',
{ workOrderIds },
)
return apiPost<{ orders: WorkOrder[] }>('/api/v1/admin/worker-platform/orders/accept-batch', {
workOrderIds,
})
}
export function deductAdminWorkOrderPendingDeposit(
+1 -5
View File
@@ -4,11 +4,7 @@ import { optimizeImageForUpload } from '@/utils/image-upload'
type UploadScope = 'admin' | 'worker' | 'collect'
export async function uploadFile(
file: File,
scene: string,
scope: UploadScope,
) {
export async function uploadFile(file: File, scene: string, scope: UploadScope) {
const uploadTarget = await optimizeImageForUpload(file, scene)
const form = new FormData()
form.append('file', uploadTarget)
+4 -15
View File
@@ -93,10 +93,7 @@ export function createWorkerWithdrawRequest(payload: {
)
}
export function changeWorkerPassword(payload: {
currentPassword: string
newPassword: string
}) {
export function changeWorkerPassword(payload: { currentPassword: string; newPassword: string }) {
return apiPost<{ worker: WorkerUser; reloginRequired: boolean }>(
'/api/v1/worker/profile/change-password',
payload,
@@ -104,10 +101,7 @@ export function changeWorkerPassword(payload: {
}
export function fetchWorkerHallOrders(params?: Record<string, unknown>) {
return apiGet<WorkerHallOrdersResponse>(
'/api/v1/worker/hall/orders',
params,
)
return apiGet<WorkerHallOrdersResponse>('/api/v1/worker/hall/orders', params)
}
export function fetchWorkerHallLeaderboard() {
@@ -115,9 +109,7 @@ export function fetchWorkerHallLeaderboard() {
}
export function grabWorkerOrder(workOrderId: number) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/worker/hall/orders/${workOrderId}/grab`,
)
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/hall/orders/${workOrderId}/grab`)
}
export function joinWorkerSharingOrder(workOrderId: number, payload: { quantity: number }) {
@@ -132,10 +124,7 @@ export function fetchWorkerMyOrders(params?: Record<string, unknown>) {
}
export function saveWorkerOrderNote(workOrderId: number, note: string) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/worker/orders/${workOrderId}/note`,
{ note },
)
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/orders/${workOrderId}/note`, { note })
}
export function submitWorkerAcceptance(
+1 -5
View File
@@ -17,11 +17,7 @@ export type { AdminDashboardSummary } from './dashboard'
export type { AdminLoginLogItem, AdminLoginLogListResult } from './login-logs'
// Orders types
export type {
AdminOrderFulfillmentProgress,
AdminOrderListItem,
AdminOrderDetail,
} from './orders'
export type { AdminOrderFulfillmentProgress, AdminOrderListItem, AdminOrderDetail } from './orders'
// Tasks types
export type {
+6 -1
View File
@@ -192,7 +192,12 @@ export interface ClaimAffiliateDashFlowInfo {
export interface ClaimDetailData {
tokenStatus: ClaimTokenStatus
claimUrl: string
flowType: 'kuaishou_cloud' | 'kuaishou_ct_assisted' | 'kuaishou_feifei' | 'affiliate_dash' | (string & {})
flowType:
| 'kuaishou_cloud'
| 'kuaishou_ct_assisted'
| 'kuaishou_feifei'
| 'affiliate_dash'
| (string & {})
claimIdentity: ClaimIdentityInfo | null
task: ClaimTaskInfo
order: ClaimOrderInfo
+18 -14
View File
@@ -31,13 +31,15 @@ export const ADMIN_TABLE_PAGINATION_BASE: Pick<
* 服务端分页列表(任务/订单/用户/审计等)。
* 始终展示 10/20/50/100,避免 antd「total ≤ 50 不显示」默认行为。
*/
export function buildAdminTablePagination(options: {
current?: number
page?: number
pageSize?: number
total?: number
onChange?: TablePaginationConfig['onChange']
} & Partial<TablePaginationConfig>): TablePaginationConfig {
export function buildAdminTablePagination(
options: {
current?: number
page?: number
pageSize?: number
total?: number
onChange?: TablePaginationConfig['onChange']
} & Partial<TablePaginationConfig>,
): TablePaginationConfig {
const { current, page, pageSize, total, onChange, ...rest } = options
return {
...ADMIN_TABLE_PAGINATION_BASE,
@@ -61,13 +63,15 @@ export function buildAdminLocalTablePagination(
}
/** kuaishou-lewan 发货记录分页(1001000 */
export function buildCloudtentaclesTablePagination(options: {
current?: number
page?: number
pageSize?: number
total?: number
onChange?: TablePaginationConfig['onChange']
} & Partial<TablePaginationConfig>): TablePaginationConfig {
export function buildCloudtentaclesTablePagination(
options: {
current?: number
page?: number
pageSize?: number
total?: number
onChange?: TablePaginationConfig['onChange']
} & Partial<TablePaginationConfig>,
): TablePaginationConfig {
const { current, page, pageSize, total, onChange, ...rest } = options
return {
showSizeChanger: true,
+1 -3
View File
@@ -18,9 +18,7 @@ export function getImageIdentity(file: {
}): string {
const objectKey = String(file?.objectKey || '').trim()
if (objectKey) return objectKey
const url = String(
file?.url || file?.mediumUrl || file?.thumbnailUrl || '',
).trim()
const url = String(file?.url || file?.mediumUrl || file?.thumbnailUrl || '').trim()
return getImageIdentityFromUrl(url)
}
+3 -12
View File
@@ -12,8 +12,7 @@ const IMAGE_UPLOAD_QUALITY: Record<string, number> = {
export async function optimizeImageForUpload(file: File, scene: string) {
if (!file.type.startsWith('image/')) return file
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type))
return file
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) return file
if (typeof document === 'undefined') return file
const maxSide = IMAGE_UPLOAD_MAX_SIDE[scene] ?? 1600
@@ -21,11 +20,7 @@ export async function optimizeImageForUpload(file: File, scene: string) {
try {
const image = await loadImage(file)
const { width, height } = fitSize(
image.naturalWidth,
image.naturalHeight,
maxSide,
)
const { width, height } = fitSize(image.naturalWidth, image.naturalHeight, maxSide)
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
@@ -70,11 +65,7 @@ function fitSize(width: number, height: number, maxSide: number) {
}
}
function canvasToBlob(
canvas: HTMLCanvasElement,
type: string,
quality: number,
) {
function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number) {
return new Promise<Blob | null>((resolve) => {
canvas.toBlob(resolve, type, quality)
})