657 lines
19 KiB
TypeScript
657 lines
19 KiB
TypeScript
import { apiDelete, apiGet, apiPost, apiPostForm, apiPut } from '@/lib/http'
|
|
import { optimizeImageForUpload } from '@/utils/image-upload'
|
|
import type {
|
|
UploadedFile,
|
|
WorkOrder,
|
|
WorkOrderEvent,
|
|
WorkOrderShare,
|
|
WorkerFinanceRequest,
|
|
WorkerCancelRequest,
|
|
WorkerCancelRequestRisk,
|
|
WorkerAnnouncementConfig,
|
|
WorkerProductNameSuffixConfig,
|
|
WorkerProductNameSuffixDisplayConfig,
|
|
WorkerAcceptanceConfig,
|
|
WorkerAcceptanceVideo,
|
|
WorkerOrderFeedback,
|
|
WorkerAfterSalesCase,
|
|
WorkerHallCategoriesResponse,
|
|
WorkerHallOrdersResponse,
|
|
WorkerLeaderboardResponse,
|
|
WorkerLeaderboardPeriod,
|
|
WorkerListResponse,
|
|
WorkerLoginResponse,
|
|
WorkerOrderOverviewResponse,
|
|
WorkerProfileResponse,
|
|
WorkerSessionDevice,
|
|
WorkerUser,
|
|
WorkerWalletLedger,
|
|
} from '@/types/worker-platform'
|
|
import type {
|
|
ConsignmentBuyerInfo,
|
|
ConsignmentContactSupporter,
|
|
ConsignmentSellerInfo,
|
|
} from '@/types/consignment'
|
|
|
|
import { getWorkerDeviceId } from '@/utils/worker-auth'
|
|
|
|
export function sendWorkerSmsCode(payload: { phone: string }) {
|
|
return apiPost<{ sent: boolean; expiresInSeconds: number; provider: string }>(
|
|
'/api/v1/worker/auth/sms-code',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function registerWorker(payload: {
|
|
phone: string
|
|
code: string
|
|
password: string
|
|
displayName: string
|
|
inviteCode?: string
|
|
}) {
|
|
return apiPost<{ worker: WorkerUser; reviewRequired: boolean }>(
|
|
'/api/v1/worker/auth/register',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function sendWorkerPasswordResetSmsCode(payload: { phone: string }) {
|
|
return apiPost<{ sent: boolean; expiresInSeconds: number; provider: string }>(
|
|
'/api/v1/worker/auth/password-reset/sms-code',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function resetWorkerPassword(payload: { phone: string; code: string; password: string }) {
|
|
return apiPost<{ worker: WorkerUser; reloginRequired: boolean }>(
|
|
'/api/v1/worker/auth/password-reset',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function loginWorker(payload: { username: string; password: string }) {
|
|
const userAgent = navigator.userAgent
|
|
const isMobile = /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent)
|
|
return apiPost<WorkerLoginResponse>('/api/v1/worker/auth/login', {
|
|
...payload,
|
|
deviceId: getWorkerDeviceId(),
|
|
deviceType: isMobile ? 'mobile' : 'pc',
|
|
deviceName: isMobile ? '手机浏览器' : 'PC 浏览器',
|
|
})
|
|
}
|
|
|
|
function buildWorkerDevicePayload() {
|
|
const userAgent = navigator.userAgent
|
|
const isMobile = /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent)
|
|
return {
|
|
deviceId: getWorkerDeviceId(),
|
|
deviceType: isMobile ? 'mobile' : 'pc',
|
|
deviceName: isMobile ? '手机浏览器' : 'PC 浏览器',
|
|
}
|
|
}
|
|
|
|
export function sendWorkerLoginSmsCode(payload: { phone: string }) {
|
|
return apiPost<{ sent: boolean; expiresInSeconds: number; provider: string }>(
|
|
'/api/v1/worker/auth/login/sms-code',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function loginWorkerBySmsCode(payload: { phone: string; code: string }) {
|
|
return apiPost<WorkerLoginResponse>('/api/v1/worker/auth/login/sms', {
|
|
...payload,
|
|
...buildWorkerDevicePayload(),
|
|
})
|
|
}
|
|
|
|
export function fetchWorkerSessions() {
|
|
return apiGet<WorkerDeviceLimitsResponse>('/api/v1/worker/auth/devices')
|
|
}
|
|
|
|
export function deleteWorkerSession(sessionId: string) {
|
|
return apiDelete<{ deleted: boolean }>(`/api/v1/worker/auth/devices/${sessionId}`)
|
|
}
|
|
|
|
export function manageWorkerSessions(payload: { username: string; password: string }) {
|
|
return apiPost<WorkerDeviceLimitsResponse>('/api/v1/worker/auth/devices/manage/list', payload)
|
|
}
|
|
|
|
export type WorkerDeviceLimitsResponse = {
|
|
maxDevices: number
|
|
maxDevicesByType: { pc: number; mobile: number }
|
|
deviceCounts: { pc: number; mobile: number }
|
|
items: WorkerSessionDevice[]
|
|
}
|
|
|
|
export function deleteWorkerSessionWithCredentials(payload: {
|
|
username: string
|
|
password: string
|
|
sessionId: string
|
|
}) {
|
|
return apiPost<{ deleted: boolean }>('/api/v1/worker/auth/devices/manage/delete', payload)
|
|
}
|
|
|
|
export function fetchWorkerSession() {
|
|
return apiGet<{
|
|
authenticated: boolean
|
|
expiresAt: string
|
|
absoluteExpiresAt?: string
|
|
worker: WorkerUser
|
|
}>('/api/v1/worker/auth/session')
|
|
}
|
|
|
|
export function logoutWorker() {
|
|
return apiPost<{ success: boolean }>('/api/v1/worker/auth/logout')
|
|
}
|
|
|
|
export function fetchWorkerProfile() {
|
|
return apiGet<WorkerProfileResponse>('/api/v1/worker/profile')
|
|
}
|
|
|
|
export function fetchWorkerAnnouncement() {
|
|
return apiGet<WorkerAnnouncementConfig>('/api/v1/worker/announcement')
|
|
}
|
|
|
|
/** 商品名来源后缀映射(后台「杂项配置」维护,只返回已启用项),打手端列表/详情/大厅展示用。 */
|
|
export function fetchWorkerProductNameSuffix() {
|
|
return apiGet<WorkerProductNameSuffixDisplayConfig>('/api/v1/worker/product-name-suffix')
|
|
}
|
|
|
|
/** 验收视频配置(后台可改):时长上限、目标高度、码率等,压缩前读取。 */
|
|
export function fetchWorkerAcceptanceConfig() {
|
|
return apiGet<WorkerAcceptanceConfig>('/api/v1/worker/acceptance/config')
|
|
}
|
|
|
|
export function fetchWorkerWalletLedgers(params?: Record<string, unknown>) {
|
|
return apiGet<WorkerListResponse<WorkerWalletLedger>>(
|
|
'/api/v1/worker/profile/wallet-ledgers',
|
|
params,
|
|
)
|
|
}
|
|
|
|
export function fetchWorkerFinanceRequests(params?: Record<string, unknown>) {
|
|
return apiGet<WorkerListResponse<WorkerFinanceRequest>>(
|
|
'/api/v1/worker/profile/finance-requests',
|
|
params,
|
|
)
|
|
}
|
|
|
|
export function createWorkerRechargeRequest(payload: {
|
|
amount?: number
|
|
note?: string
|
|
paidAt?: string
|
|
proofFiles?: UploadedFile[]
|
|
}) {
|
|
return apiPost<{ request: WorkerFinanceRequest }>(
|
|
'/api/v1/worker/profile/recharge-requests',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function createWorkerWithdrawRequest(payload: {
|
|
amount?: number
|
|
accountChannel: string
|
|
note?: string
|
|
}) {
|
|
return apiPost<{ request: WorkerFinanceRequest }>(
|
|
'/api/v1/worker/profile/withdraw-requests',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function createWorkerWithdrawalAccount(payload: {
|
|
accountChannel: string
|
|
accountName: string
|
|
accountNo?: string
|
|
alipayQrCodeImages?: UploadedFile[]
|
|
wechatQrCodeImages?: UploadedFile[]
|
|
}) {
|
|
return apiPost('/api/v1/worker/profile/withdrawal-account', payload)
|
|
}
|
|
|
|
export function saveWorkerAlipayWithdrawalQrCode(payload: { alipayQrCodeImages: UploadedFile[] }) {
|
|
return apiPut('/api/v1/worker/profile/withdrawal-account/alipay-qr-code', payload)
|
|
}
|
|
|
|
export function changeWorkerPassword(payload: { currentPassword: string; newPassword: string }) {
|
|
return apiPost<{ worker: WorkerUser; reloginRequired: boolean }>(
|
|
'/api/v1/worker/profile/change-password',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function renameWorkerAccount(payload: { displayName: string }) {
|
|
return apiPost<{ worker: WorkerUser; reloginRequired: boolean }>(
|
|
'/api/v1/worker/profile/rename',
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function fetchWorkerHallCategories() {
|
|
return apiGet<WorkerHallCategoriesResponse>('/api/v1/worker/hall/categories')
|
|
}
|
|
|
|
export function fetchWorkerHallOrders(params?: Record<string, unknown>) {
|
|
return apiGet<WorkerHallOrdersResponse>('/api/v1/worker/hall/orders', params)
|
|
}
|
|
|
|
export function fetchWorkerHallLeaderboard(period: WorkerLeaderboardPeriod = 'all') {
|
|
return apiGet<WorkerLeaderboardResponse>('/api/v1/worker/hall/leaderboard', { period })
|
|
}
|
|
|
|
export function grabWorkerOrder(workOrderId: number, payload: { requestId?: string } = {}) {
|
|
return apiPost<{ order: WorkOrder; duplicated?: boolean }>(
|
|
`/api/v1/worker/hall/orders/${workOrderId}/grab`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function joinWorkerSharingOrder(
|
|
workOrderId: number,
|
|
payload: { quantity: number; requestId?: string },
|
|
) {
|
|
return apiPost<{ share: WorkOrderShare; duplicated?: boolean }>(
|
|
`/api/v1/worker/hall/orders/${workOrderId}/join-sharing`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function fetchWorkerMyOrders(params?: Record<string, unknown>) {
|
|
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/worker/orders', params)
|
|
}
|
|
|
|
export function fetchWorkerMyOrderOverview() {
|
|
return apiGet<WorkerOrderOverviewResponse>('/api/v1/worker/orders/overview')
|
|
}
|
|
|
|
export function fetchWorkerMyOrderEvents(workOrderId: number) {
|
|
return apiGet<{ items: WorkOrderEvent[] }>(`/api/v1/worker/orders/${workOrderId}/events`)
|
|
}
|
|
|
|
export function fetchWorkerCancelRequests() {
|
|
return apiGet<{ risk: WorkerCancelRequestRisk; items: WorkerCancelRequest[] }>(
|
|
'/api/v1/worker/orders/cancel-requests',
|
|
)
|
|
}
|
|
|
|
export function submitWorkerCancelRequest(
|
|
workOrderId: number,
|
|
payload: { reason: string; shareId?: number | null },
|
|
) {
|
|
return apiPost<{ request: WorkerCancelRequest; risk: WorkerCancelRequestRisk }>(
|
|
`/api/v1/worker/orders/${workOrderId}/cancel-requests`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function fetchWorkerOrderFeedbacks() {
|
|
return apiGet<{ items: WorkerOrderFeedback[] }>('/api/v1/worker/orders/feedbacks')
|
|
}
|
|
|
|
export function fetchWorkerAfterSalesCases() {
|
|
return apiGet<{ items: WorkerAfterSalesCase[] }>('/api/v1/worker/orders/after-sales-cases')
|
|
}
|
|
|
|
export function submitWorkerAfterSalesCaseResponse(
|
|
caseId: number,
|
|
payload: { response?: string; files?: UploadedFile[] },
|
|
) {
|
|
return apiPost<{ case: WorkerAfterSalesCase }>(
|
|
`/api/v1/worker/orders/after-sales-cases/${caseId}/response`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function submitWorkerOrderFeedback(workOrderId: number, payload: { content: string }) {
|
|
return apiPost<{ feedback: WorkerOrderFeedback }>(
|
|
`/api/v1/worker/orders/${workOrderId}/feedbacks`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function markWorkerOrderRead(workOrderId: number, options: { materialSeen?: boolean } = {}) {
|
|
return apiPost<{ viewedAt: string; isUnread: false }>(
|
|
`/api/v1/worker/orders/${workOrderId}/read`,
|
|
{ materialSeen: options.materialSeen === true },
|
|
)
|
|
}
|
|
|
|
export function saveWorkerOrderNote(workOrderId: number, note: string) {
|
|
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/orders/${workOrderId}/note`, { note })
|
|
}
|
|
|
|
export type WorkerAcceptancePayload = {
|
|
note?: string
|
|
imageUrls?: string[]
|
|
files?: UploadedFile[]
|
|
/** 验收视频(非必填);null 在草稿阶段表示删除,已提交阶段服务端只接受替换 */
|
|
video?: WorkerAcceptanceVideo | null
|
|
shareId?: number
|
|
}
|
|
|
|
export function submitWorkerAcceptance(workOrderId: number, payload: WorkerAcceptancePayload) {
|
|
return apiPost<{ order: WorkOrder; share?: WorkOrderShare }>(
|
|
`/api/v1/worker/orders/${workOrderId}/submit-acceptance`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
/** 好友赠送模式:提交打手本人资料(昵称/ID/区服/系统 + 主页截图) */
|
|
export function submitWorkerGiftMaterial(
|
|
workOrderId: number,
|
|
payload: { fields: Record<string, string>; screenshots: UploadedFile[]; note?: string },
|
|
) {
|
|
return apiPost<{ order: WorkOrder }>(
|
|
`/api/v1/worker/orders/${workOrderId}/gift-material`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function remindWorkerAcceptance(workOrderId: number) {
|
|
return apiPost<{ reminderCount: number; nextRemindAt: string }>(
|
|
`/api/v1/worker/orders/${workOrderId}/remind-acceptance`,
|
|
)
|
|
}
|
|
|
|
export function saveWorkerAcceptanceDraft(workOrderId: number, payload: WorkerAcceptancePayload) {
|
|
return apiPost<{ order: WorkOrder; share?: WorkOrderShare }>(
|
|
`/api/v1/worker/orders/${workOrderId}/acceptance-draft`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
export function supplementWorkerAcceptedOrderEvidence(
|
|
workOrderId: number,
|
|
payload: { files: UploadedFile[]; video?: WorkerAcceptanceVideo | null; shareId?: number },
|
|
) {
|
|
return apiPost<{ order: WorkOrder; share?: WorkOrderShare }>(
|
|
`/api/v1/worker/orders/${workOrderId}/acceptance-evidence`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
// ---------------- 打手端:我要寄卖与手枪库存 ----------------
|
|
|
|
export type WorkerConsignmentPublishPayload = {
|
|
itemName?: string
|
|
system: string
|
|
serverZone: string
|
|
series: string
|
|
diamondLevel: number
|
|
brillianceCount: number
|
|
askingPriceFen: number
|
|
sellerContact: string
|
|
sellerPhone?: string
|
|
characterName: string
|
|
characterId: string
|
|
screenshotsJson: string[]
|
|
sellerNote?: string
|
|
depositAmountFen?: number
|
|
/** 对接客服名单 id(必填,发布时选定) */
|
|
contactSupporterId: number
|
|
}
|
|
|
|
export type WorkerConsignmentItem = {
|
|
id: number | string
|
|
submissionNo: string
|
|
itemName: string
|
|
system: string
|
|
serverZone: string
|
|
series: string
|
|
diamondLevel: number
|
|
brillianceCount: number
|
|
askingPriceFen: number
|
|
status: 'pending_review' | 'approved' | 'rejected'
|
|
reviewNote?: string
|
|
resultInventoryId?: number | string | null
|
|
screenshotsJson: string[]
|
|
contentJson: Record<string, unknown>
|
|
contactSupporterId?: number | null
|
|
contactSupporterName?: string
|
|
createdAt: string
|
|
}
|
|
|
|
/** 寄卖单统一状态机阶段(后端推导,前端只透传展示) */
|
|
export type WorkerConsignmentStage =
|
|
| 'pending_review'
|
|
| 'in_stock'
|
|
| 'matched_waiting_friend'
|
|
| 'gift_cooldown'
|
|
| 'gift_ready'
|
|
| 'gift_submitted'
|
|
| 'fulfilling'
|
|
| 'pending_acceptance'
|
|
| 'completed'
|
|
| 'rejected'
|
|
| 'archived'
|
|
| 'cancelled'
|
|
|
|
export type WorkerConsignmentGroup = 'reviewing' | 'active' | 'finished'
|
|
|
|
/**
|
|
* 「我的寄卖」列表行:**一件货一行**。
|
|
*
|
|
* 后端已把提报单(SUB…)/ 库存(INV…)/ 绑定工单三套状态聚合成一条记录,
|
|
* 以 `consignmentNo` 为唯一锚点;阶段文案、配色与可执行动作也由后端下发,
|
|
* 前端不再自己推导状态,避免和客服侧口径不一致。
|
|
*/
|
|
export type WorkerConsignmentRecord = {
|
|
/** 唯一锚点:提报单号(客服手工入库时是库存号),界面只显示它 */
|
|
consignmentNo: string
|
|
submissionId: number | null
|
|
inventoryId: number | null
|
|
workOrderId: number | null
|
|
itemName: string
|
|
system: string
|
|
serverZone: string
|
|
series: string
|
|
diamondLevel: number
|
|
brillianceCount: number
|
|
askingPriceFen: number
|
|
depositAmountFen: number
|
|
contactSupporterId?: number | null
|
|
contactSupporterName?: string
|
|
reviewNote: string
|
|
screenshotsJson: string[]
|
|
giftEvidenceImages?: string[]
|
|
giftEvidenceNote?: string
|
|
giftEvidenceSubmittedAt?: string | null
|
|
createdAt: string
|
|
stage: WorkerConsignmentStage
|
|
/** 中文阶段名,如「已匹配-待加好友」 */
|
|
label: string
|
|
group: WorkerConsignmentGroup
|
|
/** antd Tag 颜色名 */
|
|
color: string
|
|
/** 给卖家看的下一步提示,直接展示 */
|
|
hint: string
|
|
canConfirmFriend: boolean
|
|
canSubmitEvidence: boolean
|
|
evidenceButtonLabel: '提交赠送截图' | '提交验收材料'
|
|
/** 赠送冷却截止时间,前端拿它做倒计时 */
|
|
giftAvailableAt: string | null
|
|
friendAddedAt: string | null
|
|
/** 卖家自己寄卖时填写的账号/角色资料 */
|
|
sellerInfo?: ConsignmentSellerInfo | null
|
|
/** 匹配工单的买家资料(用于加好友) */
|
|
buyerInfo?: ConsignmentBuyerInfo | null
|
|
}
|
|
|
|
export type { ConsignmentBuyerInfo, ConsignmentSellerInfo } from '@/types/consignment'
|
|
|
|
export type WorkerConsignmentListCounts = {
|
|
all: number
|
|
reviewing: number
|
|
active: number
|
|
finished: number
|
|
}
|
|
|
|
export type WorkerConsignmentListResponse = {
|
|
publishDepositFen: number
|
|
items: WorkerConsignmentRecord[]
|
|
/** 当前分组 + 关键字下的总条数(服务端分页用) */
|
|
total: number
|
|
page: number
|
|
pageSize: number
|
|
/** 各分组条数,用于筛选角标;受关键字影响、不受当前分组影响 */
|
|
counts: WorkerConsignmentListCounts
|
|
}
|
|
|
|
export type WorkerConsignmentListParams = {
|
|
group?: 'all' | 'reviewing' | 'active' | 'finished'
|
|
keyword?: string
|
|
page?: number
|
|
pageSize?: number
|
|
}
|
|
|
|
export function publishWorkerConsignment(payload: WorkerConsignmentPublishPayload) {
|
|
return apiPost<WorkerConsignmentItem>('/api/v1/worker/consignment/publish', payload)
|
|
}
|
|
|
|
/** 发布寄卖时的「对接客服」下拉(仅启用中的名单)。 */
|
|
export function fetchWorkerConsignmentContacts() {
|
|
return apiGet<{ items: ConsignmentContactSupporter[] }>('/api/v1/worker/consignment/contacts')
|
|
}
|
|
|
|
export function fetchWorkerConsignments(params: WorkerConsignmentListParams = {}) {
|
|
return apiGet<WorkerConsignmentListResponse>(
|
|
'/api/v1/worker/consignment/my-items',
|
|
params as Record<string, unknown>,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 卖家确认已在游戏内加好买家好友 → 进入赠送冷却期。
|
|
* 注意:确认后若未完成赠送,履约押金不退。
|
|
*/
|
|
export function confirmWorkerConsignmentFriendAdded(submissionId: number | string) {
|
|
return apiPost<{ order: WorkOrder }>(
|
|
`/api/v1/worker/consignment/items/${submissionId}/confirm-friend`,
|
|
{},
|
|
)
|
|
}
|
|
|
|
/** 卖家提交赠送完成截图(后端复用打手验收链路,工单转「待验收」交客服审核)。 */
|
|
export function submitWorkerConsignmentGiftEvidence(
|
|
submissionId: number | string,
|
|
payload: { imageUrls: string[]; note?: string },
|
|
) {
|
|
return apiPost<{ order: WorkOrder }>(
|
|
`/api/v1/worker/consignment/items/${submissionId}/gift-evidence`,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
/** 发布寄卖:按规格(系统/系列/钻级/璀璨)从「接单模板」带出建议价,如 安卓+暗潮+1钻+0璀 → ¥120 */
|
|
export function fetchWorkerConsignmentPriceSuggestion(params: {
|
|
system: string
|
|
series: string
|
|
diamondLevel: number
|
|
brillianceCount?: number
|
|
}) {
|
|
return apiGet<{
|
|
suggestedPriceFen: number | null
|
|
matchedCount: number
|
|
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'
|
|
validation?: 'none' | 'digits'
|
|
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 OrderServiceMaterialConfirmation = {
|
|
enabled: boolean
|
|
title: string
|
|
content: string
|
|
confirmText: string
|
|
cancelText: string
|
|
}
|
|
|
|
export type OrderServiceItem = {
|
|
workOrderId: number
|
|
productName: string
|
|
categoryName: string
|
|
title: string
|
|
description: string
|
|
submitText: string
|
|
successText: string
|
|
postSubmitText: string
|
|
state: string
|
|
stateLabel: string
|
|
message: string
|
|
canSubmit: boolean
|
|
blockedReason: '' | 'already_submitted' | 'status' | 'assigned' | 'shares'
|
|
materialConfirmation?: OrderServiceMaterialConfirmation
|
|
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,
|
|
)
|
|
}
|