Files
order_site/apps/frontend/src/services/worker.ts
T
yml2213 e731348bb0 refactor(consignment): 删掉按库存号操作寄卖的卖家入口
手动入库停用后,"没有提报单的现货"已不可能再产生,批次 I 为兜这类货加的两个
按库存号入口(/worker/consignment/inventories/:inventoryId/...)成了纯负担。

- 删除两个按库存号的路由/服务函数、前端两个请求函数与页面里的锚点分派逻辑,
  卖家动作只按提报单号(寄卖单号)定位
- 卖家列表里"没有提报单"的兜底行不再暴露内部仓库号(标题改「客服手工入库」),
  且一律不给操作能力位,不给用户一个点下去必然失败的按钮
- 加静态守卫测试钉住以上两点,防止以后又长出按库存号的入口

真实库验证:卖家列表三件货都带寄卖单号与 submissionId,没有提报单的现货 0 件。
2026-09-17 11:45:50 +08:00

486 lines
14 KiB
TypeScript

import { apiDelete, apiGet, apiPost, apiPut } from '@/lib/http'
import type {
CollectLookupResponse,
UploadedFile,
WorkOrder,
WorkOrderEvent,
WorkOrderShare,
WorkerFinanceRequest,
WorkerCancelRequest,
WorkerCancelRequestRisk,
WorkerAnnouncementConfig,
WorkerOrderFeedback,
WorkerAfterSalesCase,
WorkerHallCategoriesResponse,
WorkerHallOrdersResponse,
WorkerLeaderboardResponse,
WorkerLeaderboardPeriod,
WorkerListResponse,
WorkerLoginResponse,
WorkerOrderOverviewResponse,
WorkerProfileResponse,
WorkerSessionDevice,
WorkerUser,
WorkerWalletLedger,
} from '@/types/worker-platform'
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
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 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) {
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/hall/orders/${workOrderId}/grab`)
}
export function joinWorkerSharingOrder(workOrderId: number, payload: { quantity: number }) {
return apiPost<{ share: WorkOrderShare }>(
`/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 }) {
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 function submitWorkerAcceptance(
workOrderId: number,
payload: { note?: string; imageUrls?: string[]; files?: UploadedFile[] },
) {
return apiPost<{ order: WorkOrder }>(
`/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: { note?: string; imageUrls?: string[]; files?: UploadedFile[] },
) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/worker/orders/${workOrderId}/acceptance-draft`,
payload,
)
}
export function supplementWorkerAcceptedOrderEvidence(
workOrderId: number,
payload: { files: UploadedFile[] },
) {
return apiPost<{ order: WorkOrder }>(
`/api/v1/worker/orders/${workOrderId}/acceptance-evidence`,
payload,
)
}
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 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
}
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>
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
reviewNote: string
screenshotsJson: string[]
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
}
export type WorkerConsignmentListResponse = {
publishDepositFen: number
items: WorkerConsignmentRecord[]
/** 各阶段件数,用于筛选 tab 角标 */
stageCounts: Record<string, number>
}
export function publishWorkerConsignment(payload: WorkerConsignmentPublishPayload) {
return apiPost<WorkerConsignmentItem>('/api/v1/worker/consignment/publish', payload)
}
export function fetchWorkerConsignments() {
return apiGet<WorkerConsignmentListResponse>('/api/v1/worker/consignment/my-items')
}
/**
* 卖家确认已在游戏内加好买家好友 → 进入赠送冷却期。
* 注意:确认后若未完成赠送,履约押金不退。
*/
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,
)
}