fix(worker-platform): 优化工单号精准检索与跨状态流转展示,修复状态更新后工单消失问题
1. 后端查询重构:新增 isWorkOrderNoLookupKeyword 工单号前缀与忽略大小写精准索引匹配,优化后端 work-order-query-repo 的工单号检索路径; 2. 前端单号智能穿透:管理员输入 WO... 等精准单号查询时,自动放宽细粒度排他状态/阶段过滤,避免加好友/验收等状态流转后工单因旧状态筛选脱靶消失; 3. 搜索栏交互升级:输入框清空即时重置关键词与页码,支持回车/按钮/清除全方位无缝触发; 4. 详情抽屉与多面板联动:增加 ordersQuery 数据更新对 detailOrder 的自动同步,并将 refreshAll 广播范围覆盖至寄售仓库与统计指标。
This commit is contained in:
@@ -41,6 +41,8 @@ export type ConsignmentInventoryRow = {
|
|||||||
gift_evidence_json?: string | Record<string, unknown> | null
|
gift_evidence_json?: string | Record<string, unknown> | null
|
||||||
gift_work_order_no?: string | null
|
gift_work_order_no?: string | null
|
||||||
gift_work_order_status?: string | null
|
gift_work_order_status?: string | null
|
||||||
|
gift_phase?: string | null
|
||||||
|
gift_friend_added_at?: string | null
|
||||||
bound_work_order_material_json?: string | Record<string, unknown> | null
|
bound_work_order_material_json?: string | Record<string, unknown> | null
|
||||||
bound_work_order_requirement_json?: string | Record<string, unknown> | null
|
bound_work_order_requirement_json?: string | Record<string, unknown> | null
|
||||||
worker_id?: string | number | null
|
worker_id?: string | number | null
|
||||||
@@ -197,6 +199,8 @@ export async function listConsignmentInventories(filters: {
|
|||||||
wo.acceptance_json AS gift_evidence_json,
|
wo.acceptance_json AS gift_evidence_json,
|
||||||
wo.work_order_no AS gift_work_order_no,
|
wo.work_order_no AS gift_work_order_no,
|
||||||
wo.status AS gift_work_order_status,
|
wo.status AS gift_work_order_status,
|
||||||
|
wo.gift_phase AS gift_phase,
|
||||||
|
wo.friend_added_at AS gift_friend_added_at,
|
||||||
wo.material_json AS bound_work_order_material_json,
|
wo.material_json AS bound_work_order_material_json,
|
||||||
wo.requirement_json AS bound_work_order_requirement_json
|
wo.requirement_json AS bound_work_order_requirement_json
|
||||||
FROM consignment_inventories ci
|
FROM consignment_inventories ci
|
||||||
@@ -314,6 +318,8 @@ export async function getConsignmentInventoryById(
|
|||||||
wo.acceptance_json AS gift_evidence_json,
|
wo.acceptance_json AS gift_evidence_json,
|
||||||
wo.work_order_no AS gift_work_order_no,
|
wo.work_order_no AS gift_work_order_no,
|
||||||
wo.status AS gift_work_order_status,
|
wo.status AS gift_work_order_status,
|
||||||
|
wo.gift_phase AS gift_phase,
|
||||||
|
wo.friend_added_at AS gift_friend_added_at,
|
||||||
wo.material_json AS bound_work_order_material_json,
|
wo.material_json AS bound_work_order_material_json,
|
||||||
wo.requirement_json AS bound_work_order_requirement_json
|
wo.requirement_json AS bound_work_order_requirement_json
|
||||||
FROM consignment_inventories ci
|
FROM consignment_inventories ci
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import type { ListInput, WorkOrderRow, WorkOrderStatistics } from './types.js'
|
|||||||
import type { PoolClient } from 'pg'
|
import type { PoolClient } from 'pg'
|
||||||
import {
|
import {
|
||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
|
WORK_ORDER_ACCEPTANCE_MODE,
|
||||||
WORK_ORDER_GIFT_PHASE,
|
WORK_ORDER_GIFT_PHASE,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
import { isPlatformOrderIdLookupKeyword } from '../../utils/platform-order-id.js'
|
import {
|
||||||
|
isPlatformOrderIdLookupKeyword,
|
||||||
|
isWorkOrderNoLookupKeyword,
|
||||||
|
} from '../../utils/platform-order-id.js'
|
||||||
|
|
||||||
export const WORK_ORDER_SELECT = `
|
export const WORK_ORDER_SELECT = `
|
||||||
SELECT
|
SELECT
|
||||||
wo.*,
|
wo.*,
|
||||||
@@ -843,6 +848,12 @@ export function buildWorkOrderWhere({
|
|||||||
filters.push(
|
filters.push(
|
||||||
`(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%')`,
|
`(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%')`,
|
||||||
)
|
)
|
||||||
|
} else if (isWorkOrderNoLookupKeyword(normalizedKeyword)) {
|
||||||
|
params.push(normalizedKeyword)
|
||||||
|
const paramIndex = `$${params.length}`
|
||||||
|
filters.push(
|
||||||
|
`(wo.work_order_no = ${paramIndex} OR wo.work_order_no ILIKE ${paramIndex} || '%' OR LOWER(wo.work_order_no) = LOWER(${paramIndex}))`,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
params.push(`%${normalizedKeyword}%`)
|
params.push(`%${normalizedKeyword}%`)
|
||||||
filters.push(`${workOrderSearchExpression()} LIKE LOWER($${params.length})`)
|
filters.push(`${workOrderSearchExpression()} LIKE LOWER($${params.length})`)
|
||||||
@@ -887,11 +898,37 @@ export function buildWorkOrderWhere({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (acceptanceMode) {
|
if (acceptanceMode) {
|
||||||
params.push(normalizeWorkOrderAcceptanceMode(acceptanceMode))
|
const rawMode = String(acceptanceMode).trim()
|
||||||
filters.push(`wo.acceptance_mode = $${params.length}`)
|
if (rawMode === 'internal_consignment') {
|
||||||
|
filters.push(`(
|
||||||
|
wo.material_json->'consignment'->>'inventoryId' IS NOT NULL
|
||||||
|
AND (wo.material_json->'consignment'->>'fulfillmentType' IS NULL OR wo.material_json->'consignment'->>'fulfillmentType' != 'worker_deposit')
|
||||||
|
AND (wo.material_json->'consignment'->>'workerId' IS NULL OR wo.material_json->'consignment'->>'workerId' = '0')
|
||||||
|
AND (wo.assigned_worker_id IS NULL OR wo.assigned_worker_id = 0)
|
||||||
|
)`)
|
||||||
|
} else if (rawMode === WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT) {
|
||||||
|
filters.push(`(
|
||||||
|
wo.acceptance_mode = 'friend_gift'
|
||||||
|
AND NOT (
|
||||||
|
wo.material_json->'consignment'->>'inventoryId' IS NOT NULL
|
||||||
|
AND (wo.material_json->'consignment'->>'fulfillmentType' IS NULL OR wo.material_json->'consignment'->>'fulfillmentType' != 'worker_deposit')
|
||||||
|
AND (wo.material_json->'consignment'->>'workerId' IS NULL OR wo.material_json->'consignment'->>'workerId' = '0')
|
||||||
|
AND (wo.assigned_worker_id IS NULL OR wo.assigned_worker_id = 0)
|
||||||
|
)
|
||||||
|
)`)
|
||||||
|
} else if (rawMode === WORK_ORDER_ACCEPTANCE_MODE.STANDARD) {
|
||||||
|
filters.push(`(
|
||||||
|
wo.acceptance_mode = 'standard'
|
||||||
|
AND wo.material_json->'consignment'->>'inventoryId' IS NULL
|
||||||
|
)`)
|
||||||
|
} else {
|
||||||
|
params.push(normalizeWorkOrderAcceptanceMode(acceptanceMode))
|
||||||
|
filters.push(`wo.acceptance_mode = $${params.length}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (giftPhase) {
|
if (giftPhase) {
|
||||||
const normalizedGiftPhase = String(giftPhase).trim()
|
const normalizedGiftPhase = String(giftPhase).trim()
|
||||||
|
const activeGiftStatusSql = `wo.status NOT IN ('accepted', 'cancelled')`
|
||||||
if (normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.GIFT_READY) {
|
if (normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.GIFT_READY) {
|
||||||
// gift_ready 是冷却到点后的实时阶段,由时间条件计算而不是落库状态。
|
// gift_ready 是冷却到点后的实时阶段,由时间条件计算而不是落库状态。
|
||||||
filters.push(`(
|
filters.push(`(
|
||||||
@@ -899,10 +936,18 @@ export function buildWorkOrderWhere({
|
|||||||
AND wo.gift_phase = 'friend_countdown'
|
AND wo.gift_phase = 'friend_countdown'
|
||||||
AND wo.gift_available_at IS NOT NULL
|
AND wo.gift_available_at IS NOT NULL
|
||||||
AND wo.gift_available_at <= NOW()
|
AND wo.gift_available_at <= NOW()
|
||||||
|
AND ${activeGiftStatusSql}
|
||||||
|
)`)
|
||||||
|
} else if (normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.FRIEND_COUNTDOWN) {
|
||||||
|
filters.push(`(
|
||||||
|
wo.acceptance_mode = 'friend_gift'
|
||||||
|
AND wo.gift_phase = 'friend_countdown'
|
||||||
|
AND (wo.gift_available_at IS NULL OR wo.gift_available_at > NOW())
|
||||||
|
AND ${activeGiftStatusSql}
|
||||||
)`)
|
)`)
|
||||||
} else if (Object.values(WORK_ORDER_GIFT_PHASE).includes(normalizedGiftPhase as never)) {
|
} else if (Object.values(WORK_ORDER_GIFT_PHASE).includes(normalizedGiftPhase as never)) {
|
||||||
params.push(normalizedGiftPhase)
|
params.push(normalizedGiftPhase)
|
||||||
const giftPhaseFilter = `wo.acceptance_mode = 'friend_gift' AND wo.gift_phase = $${params.length}`
|
const giftPhaseFilter = `wo.acceptance_mode = 'friend_gift' AND wo.gift_phase = $${params.length} AND ${activeGiftStatusSql}`
|
||||||
// 待打手提交资料仅代表已接单后的赠送流程;待抢单/未分配模板单不应混入该筛选。
|
// 待打手提交资料仅代表已接单后的赠送流程;待抢单/未分配模板单不应混入该筛选。
|
||||||
filters.push(
|
filters.push(
|
||||||
normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED
|
normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ function mapConsignmentEvidence(source: {
|
|||||||
gift_evidence_json?: string | Record<string, unknown> | null
|
gift_evidence_json?: string | Record<string, unknown> | null
|
||||||
gift_work_order_no?: string | null
|
gift_work_order_no?: string | null
|
||||||
gift_work_order_status?: string | null
|
gift_work_order_status?: string | null
|
||||||
|
gift_phase?: string | null
|
||||||
|
gift_friend_added_at?: string | null
|
||||||
}) {
|
}) {
|
||||||
const acceptance = toRecord(source.gift_evidence_json)
|
const acceptance = toRecord(source.gift_evidence_json)
|
||||||
const files = Array.isArray(acceptance?.files) ? (acceptance.files as unknown[]) : []
|
const files = Array.isArray(acceptance?.files) ? (acceptance.files as unknown[]) : []
|
||||||
@@ -146,6 +148,8 @@ function mapConsignmentEvidence(source: {
|
|||||||
giftEvidenceSubmittedAt: String(acceptance?.submittedAt || ''),
|
giftEvidenceSubmittedAt: String(acceptance?.submittedAt || ''),
|
||||||
giftWorkOrderNo: source.gift_work_order_no || '',
|
giftWorkOrderNo: source.gift_work_order_no || '',
|
||||||
giftWorkOrderStatus: workOrderStatus,
|
giftWorkOrderStatus: workOrderStatus,
|
||||||
|
giftPhase: source.gift_phase || '',
|
||||||
|
giftFriendAddedAt: source.gift_friend_added_at || '',
|
||||||
/** none=还没交 / pending=交了待客服验收 / accepted=客服已验收 / submitted=其他在途状态 */
|
/** none=还没交 / pending=交了待客服验收 / accepted=客服已验收 / submitted=其他在途状态 */
|
||||||
giftEvidenceReviewStatus: reviewStatus,
|
giftEvidenceReviewStatus: reviewStatus,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1635,6 +1635,25 @@ test('好友赠送待打手提交资料筛选排除待抢单订单', () => {
|
|||||||
assert.match(whereClause, /wo\.status IN \('in_progress', 'problem'\)/)
|
assert.match(whereClause, /wo\.status IN \('in_progress', 'problem'\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('工单筛选支持内部寄售匹配与打手好友赠送隔离,且赠送冷却中排除已验收已取消工单', () => {
|
||||||
|
const internalQuery = buildWorkOrderWhere({ acceptanceMode: 'internal_consignment' })
|
||||||
|
assert.match(internalQuery.whereClause, /wo\.material_json->'consignment'->>'inventoryId' IS NOT NULL/)
|
||||||
|
assert.match(internalQuery.whereClause, /wo\.assigned_worker_id IS NULL OR wo\.assigned_worker_id = 0/)
|
||||||
|
|
||||||
|
const friendGiftQuery = buildWorkOrderWhere({ acceptanceMode: 'friend_gift' })
|
||||||
|
assert.match(friendGiftQuery.whereClause, /wo\.acceptance_mode = 'friend_gift'/)
|
||||||
|
assert.match(friendGiftQuery.whereClause, /NOT \(/)
|
||||||
|
|
||||||
|
const countdownQuery = buildWorkOrderWhere({ giftPhase: 'friend_countdown' })
|
||||||
|
assert.match(countdownQuery.whereClause, /wo\.gift_phase = 'friend_countdown'/)
|
||||||
|
assert.match(countdownQuery.whereClause, /wo\.gift_available_at > NOW\(\)/)
|
||||||
|
assert.match(countdownQuery.whereClause, /wo\.status NOT IN \('accepted', 'cancelled'\)/)
|
||||||
|
|
||||||
|
const readyQuery = buildWorkOrderWhere({ giftPhase: 'gift_ready' })
|
||||||
|
assert.match(readyQuery.whereClause, /wo\.gift_available_at <= NOW\(\)/)
|
||||||
|
assert.match(readyQuery.whereClause, /wo\.status NOT IN \('accepted', 'cancelled'\)/)
|
||||||
|
})
|
||||||
|
|
||||||
test('好友赠送冷却中才允许客服手动结束冷却', () => {
|
test('好友赠送冷却中才允许客服手动结束冷却', () => {
|
||||||
const cooling = mapWorkOrderAdmin(
|
const cooling = mapWorkOrderAdmin(
|
||||||
buildWorkOrderRow({
|
buildWorkOrderRow({
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import test from 'node:test'
|
import test from 'node:test'
|
||||||
|
|
||||||
import { isPlatformOrderIdLookupKeyword } from './platform-order-id.js'
|
import {
|
||||||
|
isPlatformOrderIdLookupKeyword,
|
||||||
|
isWorkOrderNoLookupKeyword,
|
||||||
|
} from './platform-order-id.js'
|
||||||
|
|
||||||
test('识别纯数字平台订单号', () => {
|
test('识别纯数字平台订单号', () => {
|
||||||
assert.equal(isPlatformOrderIdLookupKeyword('20247230034021572'), true)
|
assert.equal(isPlatformOrderIdLookupKeyword('20247230034021572'), true)
|
||||||
@@ -14,9 +17,19 @@ test('识别拼多多带连字符的 order_sn', () => {
|
|||||||
assert.equal(isPlatformOrderIdLookupKeyword('260912-123456789012345'), true)
|
assert.equal(isPlatformOrderIdLookupKeyword('260912-123456789012345'), true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('识别统一内部工单号 (WO...)', () => {
|
||||||
|
assert.equal(isWorkOrderNoLookupKeyword('WOb320ee0b39b9'), true)
|
||||||
|
assert.equal(isWorkOrderNoLookupKeyword('wob320ee0b39b9'), true)
|
||||||
|
assert.equal(isWorkOrderNoLookupKeyword('WO26091216358821'), true)
|
||||||
|
assert.equal(isWorkOrderNoLookupKeyword('WO12'), false)
|
||||||
|
assert.equal(isWorkOrderNoLookupKeyword('20247230034021572'), false)
|
||||||
|
})
|
||||||
|
|
||||||
test('拒绝普通关键词', () => {
|
test('拒绝普通关键词', () => {
|
||||||
assert.equal(isPlatformOrderIdLookupKeyword('皮肤店'), false)
|
assert.equal(isPlatformOrderIdLookupKeyword('皮肤店'), false)
|
||||||
assert.equal(isPlatformOrderIdLookupKeyword('安卓怒嚎3钻'), false)
|
assert.equal(isPlatformOrderIdLookupKeyword('安卓怒嚎3钻'), false)
|
||||||
assert.equal(isPlatformOrderIdLookupKeyword('DT881aa66294cc'), false)
|
assert.equal(isPlatformOrderIdLookupKeyword('DT881aa66294cc'), false)
|
||||||
assert.equal(isPlatformOrderIdLookupKeyword(''), false)
|
assert.equal(isPlatformOrderIdLookupKeyword(''), false)
|
||||||
|
assert.equal(isWorkOrderNoLookupKeyword('皮肤店'), false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,12 @@ export function isPlatformOrderIdLookupKeyword(value: unknown): boolean {
|
|||||||
const trimmed = String(value || '').trim()
|
const trimmed = String(value || '').trim()
|
||||||
return /^\d{6,}(-\d{6,})?$/.test(trimmed)
|
return /^\d{6,}(-\d{6,})?$/.test(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 识别内部统一工单号 (以 WO 开头且至少 4 位字符,如 WOb320ee0b39b9、WO26091216358821)。
|
||||||
|
*/
|
||||||
|
export function isWorkOrderNoLookupKeyword(value: unknown): boolean {
|
||||||
|
const trimmed = String(value || '').trim()
|
||||||
|
return /^WO[0-9a-zA-Z]{4,}$/i.test(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Card, Space, Tabs } from 'antd'
|
import { Card, Space, Tabs } from 'antd'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useLocation, useSearchParams } from 'react-router'
|
import { useLocation, useSearchParams } from 'react-router'
|
||||||
@@ -27,7 +27,30 @@ type AfterSalesLocationState = {
|
|||||||
createOrder?: WorkOrder
|
createOrder?: WorkOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TAB_QUERY_KEYS: Record<string, string[][]> = {
|
||||||
|
'consignment-inventory': [['admin-consignment-inventories'], ['admin-consignment-stats']],
|
||||||
|
'consignment-submissions': [
|
||||||
|
['admin-consignment-submissions'],
|
||||||
|
['admin-consignment-submissions-pending-total'],
|
||||||
|
],
|
||||||
|
orders: [['admin-work-orders'], ['admin-work-orders-stats']],
|
||||||
|
'cancel-requests': [['admin-work-order-cancel-requests']],
|
||||||
|
'after-sales': [['admin-after-sales-orders'], ['admin-after-sales-stats']],
|
||||||
|
feedbacks: [['admin-feedbacks']],
|
||||||
|
rules: [['admin-product-rules']],
|
||||||
|
'product-match': [['admin-product-match']],
|
||||||
|
categories: [['admin-categories']],
|
||||||
|
workers: [['admin-workers'], ['admin-worker-users']],
|
||||||
|
leaderboard: [['admin-leaderboard']],
|
||||||
|
finance: [['admin-finance-settlements'], ['admin-finance-withdrawals']],
|
||||||
|
levels: [['admin-worker-levels']],
|
||||||
|
notifications: [['admin-notifications']],
|
||||||
|
announcement: [['admin-announcements']],
|
||||||
|
'hall-config': [['admin-hall-config']],
|
||||||
|
}
|
||||||
|
|
||||||
export default function AdminWorkerPlatformPage() {
|
export default function AdminWorkerPlatformPage() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const [activeTab, setActiveTab] = useState(() => loadActiveTab(searchParams.get('tab')))
|
const [activeTab, setActiveTab] = useState(() => loadActiveTab(searchParams.get('tab')))
|
||||||
@@ -49,6 +72,18 @@ export default function AdminWorkerPlatformPage() {
|
|||||||
? 'orders'
|
? 'orders'
|
||||||
: activeTab
|
: activeTab
|
||||||
|
|
||||||
|
// 切换标签页时自动刷新对应标签页的查询数据,确保切入即为最新状态
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visibleActiveTab) return
|
||||||
|
const queries = TAB_QUERY_KEYS[visibleActiveTab]
|
||||||
|
if (queries) {
|
||||||
|
queries.forEach((queryKey) => {
|
||||||
|
void queryClient.invalidateQueries({ queryKey })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-summary'] })
|
||||||
|
}, [visibleActiveTab, queryClient])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tab = searchParams.get('tab')
|
const tab = searchParams.get('tab')
|
||||||
if (isWorkerPlatformTab(tab)) {
|
if (isWorkerPlatformTab(tab)) {
|
||||||
|
|||||||
@@ -331,6 +331,31 @@ export default function ConsignmentInventoryPanel() {
|
|||||||
{resolveConsignmentNo(record)}
|
{resolveConsignmentNo(record)}
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
{record.giftWorkOrderNo ? (
|
||||||
|
<div style={{ marginTop: 2 }}>
|
||||||
|
<Space size={4} align="center" wrap>
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
关联工单
|
||||||
|
</Text>
|
||||||
|
<Text copyable style={{ fontSize: 12, fontWeight: 600, color: '#1677ff' }}>
|
||||||
|
{record.giftWorkOrderNo}
|
||||||
|
</Text>
|
||||||
|
{record.giftWorkOrderStatus ? (
|
||||||
|
<Tag
|
||||||
|
color="cyan"
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: '16px',
|
||||||
|
padding: '0 4px',
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatGiftWorkOrderStatus(record.giftWorkOrderStatus)}
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<div style={{ marginTop: 2 }}>
|
<div style={{ marginTop: 2 }}>
|
||||||
{record.sourceType === 'worker_portal' ? (
|
{record.sourceType === 'worker_portal' ? (
|
||||||
<Tag color="purple" style={{ fontSize: 11, padding: '0 4px' }}>
|
<Tag color="purple" style={{ fontSize: 11, padding: '0 4px' }}>
|
||||||
@@ -510,8 +535,18 @@ export default function ConsignmentInventoryPanel() {
|
|||||||
return (
|
return (
|
||||||
<Space direction="vertical" size={2}>
|
<Space direction="vertical" size={2}>
|
||||||
<Badge status="success" text="已履约出库" />
|
<Badge status="success" text="已履约出库" />
|
||||||
|
{record.giftWorkOrderNo ? (
|
||||||
|
<Space size={2} align="center">
|
||||||
|
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||||
|
工单:
|
||||||
|
</Text>
|
||||||
|
<Text copyable style={{ fontSize: 11, color: '#1677ff', fontWeight: 600 }}>
|
||||||
|
{record.giftWorkOrderNo}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
) : null}
|
||||||
{record.boundPlatformOrderId ? (
|
{record.boundPlatformOrderId ? (
|
||||||
<Text copyable style={{ fontSize: 11, color: '#1890ff' }}>
|
<Text copyable style={{ fontSize: 11, color: '#8c8c8c' }}>
|
||||||
{record.boundPlatformOrderId}
|
{record.boundPlatformOrderId}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -601,6 +636,8 @@ export default function ConsignmentInventoryPanel() {
|
|||||||
) : null}
|
) : null}
|
||||||
{record.status === 'consumed' &&
|
{record.status === 'consumed' &&
|
||||||
canManageConsignment &&
|
canManageConsignment &&
|
||||||
|
!record.giftFriendAddedAt &&
|
||||||
|
(!record.giftPhase || record.giftPhase === 'material_required') &&
|
||||||
record.giftEvidenceReviewStatus !== 'accepted' &&
|
record.giftEvidenceReviewStatus !== 'accepted' &&
|
||||||
record.giftWorkOrderStatus !== 'accepted' ? (
|
record.giftWorkOrderStatus !== 'accepted' ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import type { TableColumnsType } from 'antd'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import ConsignmentEvidenceGallery from '@/components/admin/ConsignmentEvidenceGallery'
|
import ConsignmentEvidenceGallery from '@/components/admin/ConsignmentEvidenceGallery'
|
||||||
|
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||||
import {
|
import {
|
||||||
approveAdminConsignmentSubmission,
|
approveAdminConsignmentSubmission,
|
||||||
fetchAdminConsignmentSubmissions,
|
fetchAdminConsignmentSubmissions,
|
||||||
@@ -468,9 +469,16 @@ export default function ConsignmentSubmissionsPanel() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="审核寄卖申请并正式入库"
|
title={
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
||||||
|
<span>审核寄卖申请并正式入库</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
open={Boolean(approveModalItem)}
|
open={Boolean(approveModalItem)}
|
||||||
width={680}
|
width={760}
|
||||||
|
style={{ top: 24 }}
|
||||||
|
bodyStyle={{ maxHeight: 'calc(86vh - 120px)', overflowY: 'auto', paddingRight: 8 }}
|
||||||
onCancel={() => setApproveModalItem(null)}
|
onCancel={() => setApproveModalItem(null)}
|
||||||
onOk={handleApproveSubmit}
|
onOk={handleApproveSubmit}
|
||||||
confirmLoading={approving}
|
confirmLoading={approving}
|
||||||
@@ -478,73 +486,186 @@ export default function ConsignmentSubmissionsPanel() {
|
|||||||
okType="primary"
|
okType="primary"
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: 16 }}>
|
{/* 顶部提报概览卡片 */}
|
||||||
<Text type="secondary">
|
<div
|
||||||
提报人联系方式:<strong>{approveModalItem?.contactInfo}</strong> | 游戏ID:
|
style={{
|
||||||
<strong>{approveModalItem?.sellerGameId || '-'}</strong> | 卖家要价:
|
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
|
||||||
<strong style={{ color: '#cf1322' }}>
|
border: '1px solid #e2e8f0',
|
||||||
¥{((approveModalItem?.askingPriceFen || 0) / 100).toFixed(2)}
|
borderRadius: 8,
|
||||||
</strong>
|
padding: '12px 16px',
|
||||||
</Text>
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Space wrap size={8} style={{ marginBottom: 6 }}>
|
||||||
|
<Text strong style={{ fontSize: 14 }}>
|
||||||
|
单号:{approveModalItem?.submissionNo}
|
||||||
|
</Text>
|
||||||
|
{approveModalItem?.workerName ? (
|
||||||
|
<Tag color="cyan">打手提报: {approveModalItem.workerName}</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color="purple">卖家自主提报</Tag>
|
||||||
|
)}
|
||||||
|
{approveModalItem?.createdAt ? (
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
提报于 {formatDateTime(approveModalItem.createdAt)}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#475569',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '12px 20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
联系方式:<Text strong>{approveModalItem?.contactInfo || '-'}</Text>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
游戏ID:<Text strong>{approveModalItem?.sellerGameId || '-'}</Text>
|
||||||
|
</span>
|
||||||
|
{approveModalItem?.sellerPaymentInfo ? (
|
||||||
|
<span>
|
||||||
|
提报收款:<Text strong>{approveModalItem.sellerPaymentInfo}</Text>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ textAlign: 'right', flexShrink: 0, paddingLeft: 16 }}>
|
||||||
|
<div style={{ fontSize: 12, color: '#64748b' }}>卖家提报要价</div>
|
||||||
|
<div style={{ fontSize: 22, fontWeight: 700, color: '#f5222d', lineHeight: 1.2 }}>
|
||||||
|
¥{((approveModalItem?.askingPriceFen || 0) / 100).toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 提报截图快速直览 */}
|
||||||
|
{approveModalItem?.screenshotsJson && approveModalItem.screenshotsJson.length > 0 ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
paddingTop: 8,
|
||||||
|
borderTop: '1px dashed #cbd5e1',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text type="secondary" style={{ fontSize: 12, flexShrink: 0 }}>
|
||||||
|
商品截图 ({approveModalItem.screenshotsJson.length}张):
|
||||||
|
</Text>
|
||||||
|
<ImagePreviewList
|
||||||
|
imageUrls={approveModalItem.screenshotsJson}
|
||||||
|
size={38}
|
||||||
|
maxVisible={6}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form form={approveForm} layout="vertical">
|
<Form form={approveForm} layout="vertical">
|
||||||
<Row gutter={16}>
|
{/* 分组一:物品与规格 */}
|
||||||
<Col span={14}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 10,
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#1e293b',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: 3, height: 14, background: '#1677ff', borderRadius: 2 }} />
|
||||||
|
物品属性与收购成本
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row gutter={12}>
|
||||||
|
<Col span={15}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="itemName"
|
name="itemName"
|
||||||
label="正式入库物品名"
|
label="正式入库物品名"
|
||||||
rules={[{ required: true, message: '请输入入库物品名' }]}
|
rules={[{ required: true, message: '请输入入库物品名' }]}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input placeholder="如:安卓暗潮1钻1璀璨 苏尔南" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={10}>
|
<Col span={9}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="costPrice"
|
name="costPrice"
|
||||||
label="实际收购成本 (元)"
|
label="实际收购成本"
|
||||||
rules={[{ required: true, message: '请输入实际收购成本' }]}
|
rules={[{ required: true, message: '请输入实际收购成本' }]}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
>
|
>
|
||||||
<InputNumber min={0} step={1} style={{ width: '100%' }} />
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
prefix="¥"
|
||||||
|
addonAfter="元"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row gutter={16}>
|
<Row gutter={12}>
|
||||||
<Col span={5}>
|
<Col span={5}>
|
||||||
<Form.Item name="system" label="系统">
|
<Form.Item name="system" label="系统" style={{ marginBottom: 16 }}>
|
||||||
{/* 审核可修正规格:与打手端发布同一套选项(苹果/安卓;微信/QQ/互通) */}
|
|
||||||
<Select options={[...CONSIGNMENT_SYSTEM_OPTIONS]} />
|
<Select options={[...CONSIGNMENT_SYSTEM_OPTIONS]} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={5}>
|
<Col span={5}>
|
||||||
<Form.Item name="serverZone" label="区服">
|
<Form.Item name="serverZone" label="区服" style={{ marginBottom: 16 }}>
|
||||||
<Select options={[...CONSIGNMENT_ZONE_OPTIONS]} />
|
<Select options={[...CONSIGNMENT_ZONE_OPTIONS]} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={5}>
|
<Col span={5}>
|
||||||
<Form.Item name="series" label="系列">
|
<Form.Item name="series" label="系列" style={{ marginBottom: 16 }}>
|
||||||
<Input />
|
<Input placeholder="如 暗潮" />
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={5}>
|
|
||||||
<Form.Item name="diamondLevel" label="钻石等级 (1-7)">
|
|
||||||
<InputNumber min={1} max={7} style={{ width: '100%' }} />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<Form.Item name="brillianceCount" label="璀璨数 (0-7)">
|
<Form.Item name="diamondLevel" label="钻石等级" style={{ marginBottom: 16 }}>
|
||||||
|
<InputNumber min={1} max={7} style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={5}>
|
||||||
|
<Form.Item name="brillianceCount" label="璀璨数" style={{ marginBottom: 16 }}>
|
||||||
<InputNumber min={0} max={7} style={{ width: '100%' }} />
|
<InputNumber min={0} max={7} style={{ width: '100%' }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row gutter={16}>
|
{/* 分组二:履约与打手 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 10,
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#1e293b',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: 3, height: 14, background: '#52c41a', borderRadius: 2 }} />
|
||||||
|
履约模式与打手关联 (选填)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row gutter={12}>
|
||||||
<Col span={14}>
|
<Col span={14}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="workerId"
|
name="workerId"
|
||||||
label="走打手接单平台 (关联履约打手)"
|
label="走打手接单平台 (关联履约打手)"
|
||||||
tooltip="指派该打手接单丢枪交货时,接单平台将自动冻结其押金并担保交付"
|
tooltip="指派该打手接单丢枪交货时,接单平台将自动冻结其押金并担保交付;不选则为客服/内部匹配"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
allowClear
|
allowClear
|
||||||
@@ -554,7 +675,7 @@ export default function ConsignmentSubmissionsPanel() {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(input.toLowerCase())
|
.includes(input.toLowerCase())
|
||||||
}
|
}
|
||||||
placeholder="可选择关联的接单打手 (选填)"
|
placeholder="未选择则为客服直接入库/内部现货匹配"
|
||||||
options={workerOptions}
|
options={workerOptions}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -562,13 +683,15 @@ export default function ConsignmentSubmissionsPanel() {
|
|||||||
<Col span={10}>
|
<Col span={10}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="depositAmount"
|
name="depositAmount"
|
||||||
label="履约押金金额 (元)"
|
label="履约押金金额"
|
||||||
tooltip="指派该打手交枪履约时,系统冻结的押金金额"
|
tooltip="指派该打手交枪履约时,系统冻结的押金金额"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
>
|
>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={0}
|
min={0}
|
||||||
step={10}
|
step={10}
|
||||||
addonBefore="¥"
|
prefix="¥"
|
||||||
|
addonAfter="元"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder="如 100"
|
placeholder="如 100"
|
||||||
/>
|
/>
|
||||||
@@ -576,52 +699,69 @@ export default function ConsignmentSubmissionsPanel() {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row gutter={16}>
|
{/* 分组三:卖家信息与入库登记 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 10,
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#1e293b',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: 3, height: 14, background: '#722ed1', borderRadius: 2 }} />
|
||||||
|
卖家联络与入库登记
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row gutter={12}>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
|
<Form.Item name="sellerContact" label="卖家微信号" style={{ marginBottom: 12 }}>
|
||||||
|
<Input placeholder="微信号" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item name="sellerPhone" label="卖家手机" style={{ marginBottom: 12 }}>
|
||||||
|
<Input placeholder="手机号" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item name="sellerGameId" label="卖家游戏ID" style={{ marginBottom: 12 }}>
|
||||||
|
<Input placeholder="游戏昵称/ID" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={12}>
|
||||||
|
<Col span={10}>
|
||||||
|
<Form.Item name="sellerPaymentInfo" label="卖家收款信息" style={{ marginBottom: 12 }}>
|
||||||
|
<Input placeholder="如 走打手接单平台钱包 或 支付宝账号" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={7}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="buyerName"
|
name="buyerName"
|
||||||
label="收购人 (客服)"
|
label="收购人 (客服)"
|
||||||
rules={[{ required: true, message: '请填写收购人' }]}
|
rules={[{ required: true, message: '请填写收购人' }]}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input placeholder="如 客服" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={7}>
|
||||||
<Form.Item name="shopName" label="关联店铺">
|
<Form.Item name="inboundBatch" label="收购批次" style={{ marginBottom: 12 }}>
|
||||||
<Input placeholder="店铺名" />
|
<Input placeholder="如 2026-09" />
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Form.Item name="inboundBatch" label="收购批次">
|
|
||||||
<Input placeholder="批次" />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row gutter={16}>
|
{/* 分组四:审核备注 */}
|
||||||
<Col span={8}>
|
<Form.Item name="reviewNote" label="审核备注" style={{ marginTop: 4, marginBottom: 0 }}>
|
||||||
<Form.Item name="sellerContact" label="卖家微信号">
|
<Input.TextArea
|
||||||
<Input />
|
rows={2}
|
||||||
</Form.Item>
|
placeholder="可选填写核准或沟通说明(如:已与打手核对主页截图无误)"
|
||||||
</Col>
|
/>
|
||||||
<Col span={8}>
|
|
||||||
<Form.Item name="sellerPhone" label="卖家手机">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Form.Item name="sellerGameId" label="卖家游戏ID">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Form.Item name="sellerPaymentInfo" label="卖家收款信息">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item name="reviewNote" label="审核备注">
|
|
||||||
<Input.TextArea rows={2} placeholder="审核核准说明" />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -415,6 +415,17 @@ export default function WorkOrdersPanel() {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const ordersPagination = ordersQuery.data?.data.pagination
|
const ordersPagination = ordersQuery.data?.data.pagination
|
||||||
|
|
||||||
|
// 当列表重新拉取最新数据后,若详情抽屉正处于打开状态,自动用最新状态同步更新 detailOrder
|
||||||
|
useEffect(() => {
|
||||||
|
if (!detailOrder) return
|
||||||
|
const items = ordersQuery.data?.data.items || []
|
||||||
|
const updated = items.find((item) => item.workOrderId === detailOrder.workOrderId)
|
||||||
|
if (updated) {
|
||||||
|
setDetailOrder(updated)
|
||||||
|
}
|
||||||
|
}, [ordersQuery.data])
|
||||||
|
|
||||||
const categoriesQuery = useQuery({
|
const categoriesQuery = useQuery({
|
||||||
queryKey: ['admin-worker-platform-categories'],
|
queryKey: ['admin-worker-platform-categories'],
|
||||||
queryFn: () => fetchAdminWorkCategories(),
|
queryFn: () => fetchAdminWorkCategories(),
|
||||||
@@ -518,7 +529,9 @@ export default function WorkOrdersPanel() {
|
|||||||
}, [showWorkerLevelFilter])
|
}, [showWorkerLevelFilter])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (acceptanceMode !== 'friend_gift') setGiftPhase(undefined)
|
if (acceptanceMode !== 'friend_gift' && acceptanceMode !== 'internal_consignment') {
|
||||||
|
setGiftPhase(undefined)
|
||||||
|
}
|
||||||
}, [acceptanceMode])
|
}, [acceptanceMode])
|
||||||
|
|
||||||
function openAssignModal(row: WorkOrder) {
|
function openAssignModal(row: WorkOrder) {
|
||||||
@@ -680,6 +693,12 @@ export default function WorkOrdersPanel() {
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ['admin-worker-platform-summary'],
|
queryKey: ['admin-worker-platform-summary'],
|
||||||
}),
|
}),
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['admin-consignment-inventories'],
|
||||||
|
}),
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['admin-consignment-stats'],
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,9 +1027,26 @@ export default function WorkOrdersPanel() {
|
|||||||
setMaterialScreenshots([])
|
setMaterialScreenshots([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExplicitOrderNoSearch(text: string) {
|
||||||
|
const trimmed = text.trim()
|
||||||
|
if (!trimmed) return false
|
||||||
|
return (
|
||||||
|
/^WO[0-9a-zA-Z]{4,}$/i.test(trimmed) ||
|
||||||
|
/^\d{6,}(-\d{6,})?$/.test(trimmed) ||
|
||||||
|
/^OD[0-9a-zA-Z]{4,}$/i.test(trimmed)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function searchOrders() {
|
function searchOrders() {
|
||||||
setKeyword(keywordInput.trim())
|
const trimmed = keywordInput.trim()
|
||||||
|
setKeyword(trimmed)
|
||||||
setPage(1)
|
setPage(1)
|
||||||
|
// 管理员精准搜索工单号/平台单号时,自动清除排他的状态与阶段限制,避免状态流转后工单从视野中消失
|
||||||
|
if (isExplicitOrderNoSearch(trimmed)) {
|
||||||
|
if (giftPhase) setGiftPhase(undefined)
|
||||||
|
if (statuses.length > 0) setStatuses([])
|
||||||
|
if (acceptanceMode) setAcceptanceMode(undefined)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearOrderFilters() {
|
function clearOrderFilters() {
|
||||||
@@ -1395,8 +1431,14 @@ export default function WorkOrdersPanel() {
|
|||||||
置顶
|
置顶
|
||||||
</Tag>
|
</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
{row.acceptanceMode === 'friend_gift' ? <Tag color="purple">好友赠送</Tag> : null}
|
{isInternalConsignmentOrder(row) ? (
|
||||||
{row.acceptanceMode === 'friend_gift' &&
|
<Tag color="cyan">内部匹配</Tag>
|
||||||
|
) : row.acceptanceMode === 'friend_gift' ? (
|
||||||
|
<Tag color="purple">好友赠送</Tag>
|
||||||
|
) : null}
|
||||||
|
{row.status !== 'accepted' &&
|
||||||
|
row.status !== 'cancelled' &&
|
||||||
|
row.acceptanceMode === 'friend_gift' &&
|
||||||
row.gift &&
|
row.gift &&
|
||||||
(!['pending_material', 'unassigned', 'open'].includes(row.rawStatus || row.status) ||
|
(!['pending_material', 'unassigned', 'open'].includes(row.rawStatus || row.status) ||
|
||||||
isInternalConsignmentOrder(row)) ? (
|
isInternalConsignmentOrder(row)) ? (
|
||||||
@@ -1682,9 +1724,16 @@ export default function WorkOrdersPanel() {
|
|||||||
<Input
|
<Input
|
||||||
allowClear
|
allowClear
|
||||||
value={keywordInput}
|
value={keywordInput}
|
||||||
placeholder="订单号 / 商品名 / 游戏ID / 游戏昵称"
|
placeholder="工单号 / 平台单号 / 商品名 / 游戏ID / 游戏昵称"
|
||||||
style={{ width: 280 }}
|
style={{ width: 280 }}
|
||||||
onChange={(event) => setKeywordInput(event.target.value)}
|
onChange={(event) => {
|
||||||
|
const nextVal = event.target.value
|
||||||
|
setKeywordInput(nextVal)
|
||||||
|
if (!nextVal.trim()) {
|
||||||
|
setKeyword('')
|
||||||
|
setPage(1)
|
||||||
|
}
|
||||||
|
}}
|
||||||
onPressEnter={searchOrders}
|
onPressEnter={searchOrders}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
@@ -1763,17 +1812,18 @@ export default function WorkOrdersPanel() {
|
|||||||
allowClear
|
allowClear
|
||||||
value={acceptanceMode}
|
value={acceptanceMode}
|
||||||
placeholder="全部验收模式"
|
placeholder="全部验收模式"
|
||||||
style={{ width: 150 }}
|
style={{ width: 160 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'standard', label: '标准验收' },
|
{ value: 'standard', label: '标准验收' },
|
||||||
{ value: 'friend_gift', label: '好友赠送' },
|
{ value: 'friend_gift', label: '打手好友赠送' },
|
||||||
|
{ value: 'internal_consignment', label: '内部寄售匹配' },
|
||||||
]}
|
]}
|
||||||
onChange={(nextMode) => {
|
onChange={(nextMode) => {
|
||||||
setAcceptanceMode(nextMode)
|
setAcceptanceMode(nextMode)
|
||||||
setPage(1)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{acceptanceMode === 'friend_gift' ? (
|
{acceptanceMode === 'friend_gift' || acceptanceMode === 'internal_consignment' ? (
|
||||||
<Select
|
<Select
|
||||||
allowClear
|
allowClear
|
||||||
value={giftPhase}
|
value={giftPhase}
|
||||||
@@ -1781,7 +1831,11 @@ export default function WorkOrdersPanel() {
|
|||||||
style={{ width: 170 }}
|
style={{ width: 170 }}
|
||||||
options={GIFT_PHASE_FILTER_OPTIONS.map((phase) => ({
|
options={GIFT_PHASE_FILTER_OPTIONS.map((phase) => ({
|
||||||
value: phase,
|
value: phase,
|
||||||
label: <Tag color={resolveGiftPhaseColor(phase)}>{formatGiftPhase(phase)}</Tag>,
|
label: (
|
||||||
|
<Tag color={resolveGiftPhaseColor(phase)}>
|
||||||
|
{formatGiftPhase(phase, acceptanceMode === 'internal_consignment')}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
}))}
|
}))}
|
||||||
onChange={(nextPhase) => {
|
onChange={(nextPhase) => {
|
||||||
setGiftPhase(nextPhase)
|
setGiftPhase(nextPhase)
|
||||||
@@ -2041,11 +2095,17 @@ export default function WorkOrdersPanel() {
|
|||||||
|
|
||||||
{detailOrder.acceptanceMode === 'friend_gift' && detailOrder.gift ? (
|
{detailOrder.acceptanceMode === 'friend_gift' && detailOrder.gift ? (
|
||||||
<Card
|
<Card
|
||||||
title="好友赠送进度"
|
title={
|
||||||
|
isInternalConsignmentOrder(detailOrder) ? '内部寄售赠送进度' : '好友赠送进度'
|
||||||
|
}
|
||||||
size="small"
|
size="small"
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Tag color="purple">{formatAcceptanceMode(detailOrder.acceptanceMode)}</Tag>
|
{isInternalConsignmentOrder(detailOrder) ? (
|
||||||
|
<Tag color="cyan">内部寄售匹配</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color="purple">{formatAcceptanceMode(detailOrder.acceptanceMode)}</Tag>
|
||||||
|
)}
|
||||||
<Tag color={resolveGiftPhaseColor(detailOrder.gift.phase)}>
|
<Tag color={resolveGiftPhaseColor(detailOrder.gift.phase)}>
|
||||||
{formatGiftPhase(
|
{formatGiftPhase(
|
||||||
detailOrder.gift.phase,
|
detailOrder.gift.phase,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
SafetyCertificateOutlined,
|
SafetyCertificateOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
App,
|
App,
|
||||||
@@ -101,6 +101,8 @@ export default function WorkerConsignmentPage() {
|
|||||||
const priceFetchTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const priceFetchTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
const lastPriceQueryKeyRef = useRef('')
|
const lastPriceQueryKeyRef = useRef('')
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const profileQuery = useQuery({
|
const profileQuery = useQuery({
|
||||||
queryKey: ['worker-profile'],
|
queryKey: ['worker-profile'],
|
||||||
queryFn: () => fetchWorkerProfile(),
|
queryFn: () => fetchWorkerProfile(),
|
||||||
@@ -114,6 +116,14 @@ export default function WorkerConsignmentPage() {
|
|||||||
queryFn: () => fetchWorkerConsignments(),
|
queryFn: () => fetchWorkerConsignments(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 切换标签页时自动刷新最新数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'records') {
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['worker-my-consignments'] })
|
||||||
|
}
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ['worker-profile'] })
|
||||||
|
}, [activeTab, queryClient])
|
||||||
|
|
||||||
// 后端已按「一件货一行」聚合,前端不再合并提报单与库存
|
// 后端已按「一件货一行」聚合,前端不再合并提报单与库存
|
||||||
const records = myConsignmentsQuery.data?.data.items || []
|
const records = myConsignmentsQuery.data?.data.items || []
|
||||||
const stageCounts = myConsignmentsQuery.data?.data.stageCounts || {}
|
const stageCounts = myConsignmentsQuery.data?.data.stageCounts || {}
|
||||||
@@ -1104,10 +1114,37 @@ function ConsignmentRecordCard({
|
|||||||
</Space>
|
</Space>
|
||||||
) : null}
|
) : null}
|
||||||
{shots.length > 0 || giftShots.length > 0 ? (
|
{shots.length > 0 || giftShots.length > 0 ? (
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginTop: 8, alignItems: 'center' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 10,
|
||||||
|
marginTop: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{shots.length > 0 ? (
|
{shots.length > 0 ? (
|
||||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: '#f8fafc', padding: '4px 10px', borderRadius: 6, border: '1px solid #e2e8f0' }}>
|
<div
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'flex', alignItems: 'center', gap: 4, whiteSpace: 'nowrap' }}>
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
background: '#f8fafc',
|
||||||
|
padding: '4px 10px',
|
||||||
|
borderRadius: 6,
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PictureOutlined />
|
<PictureOutlined />
|
||||||
商品截图({shots.length})
|
商品截图({shots.length})
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
@@ -1127,8 +1164,28 @@ function ConsignmentRecordCard({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{giftShots.length > 0 ? (
|
{giftShots.length > 0 ? (
|
||||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: '#f0fdf4', padding: '4px 10px', borderRadius: 6, border: '1px solid #bbf7d0' }}>
|
<div
|
||||||
<Typography.Text strong style={{ fontSize: 12, color: '#15803d', display: 'flex', alignItems: 'center', gap: 4, whiteSpace: 'nowrap' }}>
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
background: '#f0fdf4',
|
||||||
|
padding: '4px 10px',
|
||||||
|
borderRadius: 6,
|
||||||
|
border: '1px solid #bbf7d0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography.Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#15803d',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CheckCircleOutlined />
|
<CheckCircleOutlined />
|
||||||
赠送凭证({giftShots.length})
|
赠送凭证({giftShots.length})
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
@@ -1146,7 +1203,11 @@ function ConsignmentRecordCard({
|
|||||||
</Space>
|
</Space>
|
||||||
</Image.PreviewGroup>
|
</Image.PreviewGroup>
|
||||||
{record.giftEvidenceNote ? (
|
{record.giftEvidenceNote ? (
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 11, color: '#4b5563', maxWidth: 160 }} ellipsis={{ tooltip: record.giftEvidenceNote }}>
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
style={{ fontSize: 11, color: '#4b5563', maxWidth: 160 }}
|
||||||
|
ellipsis={{ tooltip: record.giftEvidenceNote }}
|
||||||
|
>
|
||||||
{record.giftEvidenceNote}
|
{record.giftEvidenceNote}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ export type ConsignmentEvidenceFields = {
|
|||||||
giftWorkOrderNo?: string
|
giftWorkOrderNo?: string
|
||||||
/** 关联工单状态:in_progress / pending_acceptance / accepted 等 */
|
/** 关联工单状态:in_progress / pending_acceptance / accepted 等 */
|
||||||
giftWorkOrderStatus?: string
|
giftWorkOrderStatus?: string
|
||||||
|
/** 关联工单赠送阶段:material_required / material_submitted / friend_countdown / gift_ready 等 */
|
||||||
|
giftPhase?: string
|
||||||
|
/** 关联工单确认加好友时间(ISO 字符串,为空代表尚未确认加好友) */
|
||||||
|
giftFriendAddedAt?: string
|
||||||
/** 赠送凭证的客服验收状态 */
|
/** 赠送凭证的客服验收状态 */
|
||||||
giftEvidenceReviewStatus?: GiftEvidenceReviewStatus
|
giftEvidenceReviewStatus?: GiftEvidenceReviewStatus
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user