统一前后端代码格式化配置
This commit is contained in:
Generated
+17
@@ -24,6 +24,7 @@
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"prettier": "3.8.3",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.0.7"
|
||||
}
|
||||
@@ -2261,6 +2262,22 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
|
||||
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"format": "prettier --config ../../.prettierrc.json --ignore-path ../../.prettierignore --write \"src/**/*.{ts,tsx,js,jsx,json,css}\" \"tests/**/*.{ts,tsx,js,jsx}\" package.json tsconfig.json vite.config.ts index.html",
|
||||
"format:check": "prettier --config ../../.prettierrc.json --ignore-path ../../.prettierignore --check \"src/**/*.{ts,tsx,js,jsx,json,css}\" \"tests/**/*.{ts,tsx,js,jsx}\" package.json tsconfig.json vite.config.ts index.html",
|
||||
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
||||
"typecheck": "tsc -b --noEmit",
|
||||
"check": "npm run format:check && npm run typecheck && npm test && npm run build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -27,6 +30,7 @@
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"prettier": "3.8.3",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.0.7"
|
||||
}
|
||||
|
||||
@@ -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-DD;showTime 时 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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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' }],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 发货记录分页(100–1000) */
|
||||
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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -11,7 +11,10 @@ test('旧打手地址映射到子域名短路径', () => {
|
||||
})
|
||||
|
||||
test('旧打手地址保留查询参数', () => {
|
||||
assert.equal(resolveLegacyWorkerPath('#/worker/orders?status=in_progress'), '/orders?status=in_progress')
|
||||
assert.equal(
|
||||
resolveLegacyWorkerPath('#/worker/orders?status=in_progress'),
|
||||
'/orders?status=in_progress',
|
||||
)
|
||||
})
|
||||
|
||||
test('非打手地址不触发跨域跳转', () => {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -18,13 +18,7 @@ export default defineConfig({
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
// 隧道/自定义域名访问 dev server 时需加入白名单,否则 Vite 会拦截 Host
|
||||
allowedHosts: [
|
||||
'localhost',
|
||||
'.localhost',
|
||||
'127.0.0.1',
|
||||
'221329.cc.cd',
|
||||
'.cc.cd',
|
||||
],
|
||||
allowedHosts: ['localhost', '.localhost', '127.0.0.1', '221329.cc.cd', '.cc.cd'],
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: apiTarget,
|
||||
|
||||
Reference in New Issue
Block a user