feat(order-service): 重构分类资料收集与订单状态链路

This commit is contained in:
yml2213
2026-11-03 19:46:49 +08:00
parent 6e4758a3c7
commit 9f14e8de7a
59 changed files with 4866 additions and 1734 deletions
@@ -1108,3 +1108,129 @@ export function revokeAdminWorkerRiskBan(banId: string, reason?: string) {
{ reason },
)
}
export type OrderServiceField = {
key: string
label: string
type: 'text' | 'textarea' | 'select' | 'radio' | 'number' | 'date'
required: boolean
placeholder?: string
helpText?: string
options?: string[]
sortOrder?: number
}
export type AdminOrderServiceProfile = {
id: number
categoryId: number
name: string
title: string
description: string
guideImage: UploadedFile | Record<string, unknown>
fields: OrderServiceField[]
screenshots: {
enabled: boolean
required: boolean
minCount: number
maxCount: number
label: string
helpText?: string
}
autoPublish: {
enabled: boolean
requireVoucherConsume: boolean
retryMax: number
}
submitText: string
successText: string
status: 'draft' | 'published' | 'archived' | string
currentVersionNo: number | null
updatedAt: string
}
export type AdminOrderServiceCategory = {
categoryId: number
categoryKey: string
name: string
parentId?: number | null
status: string
}
export type AdminOrderServiceProfileList = {
categories: AdminOrderServiceCategory[]
profiles: AdminOrderServiceProfile[]
}
export function fetchAdminOrderServiceProfiles() {
return apiGet<AdminOrderServiceProfileList>(
'/api/v1/admin/worker-platform/order-service/profiles',
)
}
export function saveAdminOrderServiceProfile(payload: {
categoryId: number
name: string
title?: string
description?: string
guideImage?: UploadedFile | Record<string, unknown>
fields: OrderServiceField[]
screenshots: {
enabled: boolean
required: boolean
minCount: number
maxCount: number
label: string
helpText?: string
}
autoPublish: {
enabled: boolean
requireVoucherConsume: boolean
retryMax: number
}
submitText?: string
successText?: string
}) {
return apiPost<{ profile: AdminOrderServiceProfile }>(
'/api/v1/admin/worker-platform/order-service/profiles',
payload,
)
}
export function publishAdminOrderServiceProfile(profileId: number) {
return apiPost<{ versionNo: number }>(
`/api/v1/admin/worker-platform/order-service/profiles/${profileId}/publish`,
)
}
export function archiveAdminOrderServiceProfile(profileId: number) {
return apiPost<{ profile: AdminOrderServiceProfile }>(
`/api/v1/admin/worker-platform/order-service/profiles/${profileId}/archive`,
)
}
export type AdminOrderServiceAutoPublishJob = {
id: number
workOrderId: number
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'skipped' | string
attemptCount: number
maxAttempts: number
nextRunAt: string | null
lastError: string
productName: string
platformOrderId: string
workOrderStatus: string
updatedAt: string | null
}
export function fetchAdminOrderServiceAutoPublishJobs(status = 'failed') {
return apiGet<{ items: AdminOrderServiceAutoPublishJob[] }>(
'/api/v1/admin/worker-platform/order-service/auto-publish-jobs',
{ status },
)
}
export function retryAdminOrderServiceAutoPublishJob(jobId: number) {
return apiPost<{ jobId: number }>(
`/api/v1/admin/worker-platform/order-service/auto-publish-jobs/${jobId}/retry`,
)
}
+4 -30
View File
@@ -5,44 +5,18 @@ import { optimizeImageForUpload } from '@/utils/image-upload'
type UploadScope = 'admin' | 'worker' | 'collect'
export async function uploadFile(file: File, scene: string, scope: UploadScope) {
if (scope === 'collect') {
throw new Error('订单服务页面必须使用订单号上传入口')
}
const uploadTarget = await optimizeImageForUpload(file, scene)
const form = new FormData()
form.append('file', uploadTarget)
form.append('scene', scene)
const endpoint =
scope === 'admin'
? '/api/v1/admin/files/upload'
: scope === 'collect'
? '/api/v1/collect/upload'
: '/api/v1/worker/files/upload'
const endpoint = scope === 'admin' ? '/api/v1/admin/files/upload' : '/api/v1/worker/files/upload'
const response = await apiPostForm<{ file: UploadedFile }>(endpoint, form)
return response.data.file
}
/**
* 一次性补资料链接的截图上传。
*
* 与公开 collect/upload 的区别:路径里带平台订单号,多工单时再带 workOrderId;
* 后端会校验对应链接是否仍可用,链接已使用/过期时不会落文件。
*/
export async function uploadCollectMaterialLinkFile(
file: File,
scene: string,
code: string,
workOrderId?: number,
) {
const uploadTarget = await optimizeImageForUpload(file, scene)
const form = new FormData()
form.append('file', uploadTarget)
form.append('scene', scene)
const query = workOrderId ? `?workOrderId=${encodeURIComponent(String(workOrderId))}` : ''
const response = await apiPostForm<{ file: UploadedFile }>(
`/api/v1/collect/link/${encodeURIComponent(code)}/upload${query}`,
form,
)
return response.data.file
}
/**
* 验收视频上传:视频已在浏览器内压好,这里不再做图片压缩。
* 带上 durationSeconds 供服务端在容器解析不到时长时兜底(见 resolveVideoDuration)。
+90 -38
View File
@@ -1,8 +1,6 @@
import { apiDelete, apiGet, apiPost, apiPut } from '@/lib/http'
import { apiDelete, apiGet, apiPost, apiPostForm, apiPut } from '@/lib/http'
import { optimizeImageForUpload } from '@/utils/image-upload'
import type {
CollectLookupResponse,
CollectMaterialLinkInfo,
CollectMaterialLinkSubmitResult,
UploadedFile,
WorkOrder,
WorkOrderEvent,
@@ -372,40 +370,6 @@ export function supplementWorkerAcceptedOrderEvidence(
)
}
export function lookupCollectOrder(orderNo: string) {
return apiPost<CollectLookupResponse>('/api/v1/collect/lookup', { orderNo })
}
export function submitCollectOrder(payload: {
orderNo: string
workOrderId?: number
fields: Record<string, string>
screenshots?: UploadedFile[]
}) {
return apiPost<{ complete: boolean; order: Record<string, unknown> }>(
'/api/v1/collect/submit',
payload,
)
}
export function fetchCollectMaterialLink(code: string) {
return apiGet<CollectMaterialLinkInfo>(`/api/v1/collect/link/${encodeURIComponent(code)}`)
}
export function submitCollectMaterialLink(
code: string,
payload: {
workOrderId?: number
fields: Record<string, string>
screenshots?: UploadedFile[]
},
) {
return apiPost<CollectMaterialLinkSubmitResult>(
`/api/v1/collect/link/${encodeURIComponent(code)}/submit`,
payload,
)
}
// ---------------- 打手端:我要寄卖与手枪库存 ----------------
export type WorkerConsignmentPublishPayload = {
@@ -591,3 +555,91 @@ export function fetchWorkerConsignmentPriceSuggestion(params: {
matchedRules: { ruleKey: string; productName: string; rewardAmountFen: number }[]
}>('/api/v1/worker/consignment/price-suggestion', params)
}
export type OrderServiceField = {
key: string
label: string
type: 'text' | 'textarea' | 'select' | 'radio' | 'number' | 'date'
required: boolean
placeholder?: string
helpText?: string
options?: string[]
sortOrder?: number
}
export type OrderServiceScreenshots = {
enabled: boolean
required: boolean
minCount: number
maxCount: number
label: string
helpText?: string
}
export type OrderServiceItem = {
workOrderId: number
productName: string
categoryName: string
title: string
description: string
submitText: string
successText: string
state: string
stateLabel: string
message: string
canSubmit: boolean
blockedReason: '' | 'already_submitted' | 'status' | 'assigned' | 'shares'
fields?: OrderServiceField[]
values?: Record<string, string>
screenshots?: UploadedFile[]
screenshotsRule?: OrderServiceScreenshots
guideImage?: UploadedFile | Record<string, unknown>
}
export type OrderServiceInfo = {
platformOrderId: string
items: OrderServiceItem[]
pendingCount: number
}
export type OrderServiceSubmitResult = {
complete: boolean
workOrderId: number
successText: string
order: { platformOrderId: string; productName: string; status: string }
}
export function fetchOrderService(orderNo: string) {
return apiGet<OrderServiceInfo>(`/api/v1/o/${encodeURIComponent(orderNo)}`)
}
export function submitOrderServiceMaterial(
orderNo: string,
payload: {
workOrderId?: number
fields: Record<string, string>
screenshots?: UploadedFile[]
},
) {
return apiPost<OrderServiceSubmitResult>(
`/api/v1/o/${encodeURIComponent(orderNo)}/material`,
payload,
)
}
export async function uploadOrderServiceFile(
file: File,
scene: string,
orderNo: string,
workOrderId?: number,
) {
const optimized = await optimizeImageForUpload(file, scene)
const form = new FormData()
form.append('file', optimized)
form.append('scene', scene)
const query = workOrderId ? `?workOrderId=${encodeURIComponent(String(workOrderId))}` : ''
return apiPostForm<{ file: UploadedFile }>(
`/api/v1/o/${encodeURIComponent(orderNo)}/files${query}`,
form,
)
}