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('/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('/api/v1/worker/auth/login/sms', { ...payload, ...buildWorkerDevicePayload(), }) } export function fetchWorkerSessions() { return apiGet('/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('/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('/api/v1/worker/profile') } export function fetchWorkerAnnouncement() { return apiGet('/api/v1/worker/announcement') } /** 商品名来源后缀映射(后台「杂项配置」维护,只返回已启用项),打手端列表/详情/大厅展示用。 */ export function fetchWorkerProductNameSuffix() { return apiGet('/api/v1/worker/product-name-suffix') } /** 验收视频配置(后台可改):时长上限、目标高度、码率等,压缩前读取。 */ export function fetchWorkerAcceptanceConfig() { return apiGet('/api/v1/worker/acceptance/config') } export function fetchWorkerWalletLedgers(params?: Record) { return apiGet>( '/api/v1/worker/profile/wallet-ledgers', params, ) } export function fetchWorkerFinanceRequests(params?: Record) { return apiGet>( '/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('/api/v1/worker/hall/categories') } export function fetchWorkerHallOrders(params?: Record) { return apiGet('/api/v1/worker/hall/orders', params) } export function fetchWorkerHallLeaderboard(period: WorkerLeaderboardPeriod = 'all') { return apiGet('/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) { return apiGet>('/api/v1/worker/orders', params) } export function fetchWorkerMyOrderOverview() { return apiGet('/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; 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 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('/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( '/api/v1/worker/consignment/my-items', params as Record, ) } /** * 卖家确认已在游戏内加好买家好友 → 进入赠送冷却期。 * 注意:确认后若未完成赠送,履约押金不退。 */ 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 screenshots?: UploadedFile[] screenshotsRule?: OrderServiceScreenshots guideImage?: UploadedFile | Record } 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(`/api/v1/o/${encodeURIComponent(orderNo)}`) } export function submitOrderServiceMaterial( orderNo: string, payload: { workOrderId?: number fields: Record screenshots?: UploadedFile[] }, ) { return apiPost( `/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, ) }