增加接单分类和大厅分类配额
This commit is contained in:
@@ -101,5 +101,21 @@ test('大厅容量先确定全局候选集合,再应用页面筛选条件', ()
|
|||||||
assert.match(query.whereClause, /LIMIT \$4/)
|
assert.match(query.whereClause, /LIMIT \$4/)
|
||||||
const candidateSql = query.whereClause.slice(query.whereClause.indexOf('candidate.status'))
|
const candidateSql = query.whereClause.slice(query.whereClause.indexOf('candidate.status'))
|
||||||
assert.doesNotMatch(candidateSql, /candidate\.category_id|candidate.*测试商品/)
|
assert.doesNotMatch(candidateSql, /candidate\.category_id|candidate.*测试商品/)
|
||||||
assert.deepEqual(query.params, [['open'], '%测试商品%', 3, 50, '2026-08-21T00:00:00.000Z'])
|
assert.deepEqual(query.params, [['open'], '%测试商品%', 3, 50, '{}', '2026-08-21T00:00:00.000Z'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('后台接单工单支持按接单分类筛选', () => {
|
||||||
|
const query = buildWorkOrderWhere({ categoryId: 7 })
|
||||||
|
assert.match(query.whereClause, /wo\.category_id = \$1/)
|
||||||
|
assert.deepEqual(query.params, [7])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('大厅候选按分类上限取队首后再受全局上限限制', () => {
|
||||||
|
const query = buildWorkOrderWhere({
|
||||||
|
hallCandidateLimit: 20,
|
||||||
|
hallCategoryLimits: { '1': 3, '2': 5 },
|
||||||
|
})
|
||||||
|
assert.match(query.whereClause, /ROW_NUMBER\(\) OVER/)
|
||||||
|
assert.match(query.whereClause, /category_rank/)
|
||||||
|
assert.deepEqual(query.params, [20, '{"1":3,"2":5}'])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ export type ListInput = {
|
|||||||
workerSharingId?: number
|
workerSharingId?: number
|
||||||
/** 大厅候选集合的总展示上限;筛选与分页在候选集合内执行。 */
|
/** 大厅候选集合的总展示上限;筛选与分页在候选集合内执行。 */
|
||||||
hallCandidateLimit?: number
|
hallCandidateLimit?: number
|
||||||
|
hallCategoryLimits?: Record<string, number>
|
||||||
visibleAfterIso?: string
|
visibleAfterIso?: string
|
||||||
/** 抢单大厅隐藏已被拼完的工单。 */
|
/** 抢单大厅隐藏已被拼完的工单。 */
|
||||||
excludeFilledSharing?: boolean
|
excludeFilledSharing?: boolean
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export async function grabWorkOrder(input: {
|
|||||||
depositAmount: number
|
depositAmount: number
|
||||||
maxActiveOrders: number
|
maxActiveOrders: number
|
||||||
hallCandidateLimit?: number
|
hallCandidateLimit?: number
|
||||||
|
hallCategoryLimits?: Record<string, number>
|
||||||
visibleAfterIso?: string
|
visibleAfterIso?: string
|
||||||
deadlineAt: string | null
|
deadlineAt: string | null
|
||||||
now: string
|
now: string
|
||||||
@@ -42,6 +43,7 @@ export async function grabWorkOrder(input: {
|
|||||||
!(await isWorkOrderWithinHallCapacityWithClient(client, {
|
!(await isWorkOrderWithinHallCapacityWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
hallCandidateLimit: input.hallCandidateLimit,
|
hallCandidateLimit: input.hallCandidateLimit,
|
||||||
|
...(input.hallCategoryLimits ? { hallCategoryLimits: input.hallCategoryLimits } : {}),
|
||||||
visibleAfterIso: input.visibleAfterIso,
|
visibleAfterIso: input.visibleAfterIso,
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export async function listWorkOrders({
|
|||||||
categoryId = 0,
|
categoryId = 0,
|
||||||
workerSharingId = 0,
|
workerSharingId = 0,
|
||||||
hallCandidateLimit = 0,
|
hallCandidateLimit = 0,
|
||||||
|
hallCategoryLimits = {},
|
||||||
visibleAfterIso = '',
|
visibleAfterIso = '',
|
||||||
excludeFilledSharing = false,
|
excludeFilledSharing = false,
|
||||||
vipEvidencePending = false,
|
vipEvidencePending = false,
|
||||||
@@ -247,6 +248,7 @@ export async function listWorkOrders({
|
|||||||
categoryId,
|
categoryId,
|
||||||
workerSharingId,
|
workerSharingId,
|
||||||
hallCandidateLimit,
|
hallCandidateLimit,
|
||||||
|
hallCategoryLimits,
|
||||||
visibleAfterIso,
|
visibleAfterIso,
|
||||||
excludeFilledSharing,
|
excludeFilledSharing,
|
||||||
vipEvidencePending,
|
vipEvidencePending,
|
||||||
@@ -652,6 +654,7 @@ export function buildWorkOrderWhere({
|
|||||||
categoryId = 0,
|
categoryId = 0,
|
||||||
workerSharingId = 0,
|
workerSharingId = 0,
|
||||||
hallCandidateLimit = 0,
|
hallCandidateLimit = 0,
|
||||||
|
hallCategoryLimits = {},
|
||||||
visibleAfterIso = '',
|
visibleAfterIso = '',
|
||||||
excludeFilledSharing = false,
|
excludeFilledSharing = false,
|
||||||
vipEvidencePending = false,
|
vipEvidencePending = false,
|
||||||
@@ -843,7 +846,9 @@ export function buildWorkOrderWhere({
|
|||||||
}
|
}
|
||||||
if (hallCandidateLimit > 0) {
|
if (hallCandidateLimit > 0) {
|
||||||
params.push(hallCandidateLimit)
|
params.push(hallCandidateLimit)
|
||||||
filters.push(`wo.id IN (${buildHallCandidateIdsSql(`$${params.length}`)})`)
|
const limitPlaceholder = `$${params.length}`
|
||||||
|
params.push(JSON.stringify(hallCategoryLimits))
|
||||||
|
filters.push(`wo.id IN (${buildHallCandidateIdsSql(limitPlaceholder, `$${params.length}`)})`)
|
||||||
}
|
}
|
||||||
if (visibleAfterIso) {
|
if (visibleAfterIso) {
|
||||||
params.push(visibleAfterIso)
|
params.push(visibleAfterIso)
|
||||||
@@ -863,23 +868,34 @@ export function buildWorkOrderWhere({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 大厅容量先选出全局队首,分类、关键词和 VIP 延迟只能在该集合内继续筛选。 */
|
/** 大厅容量先选出全局队首,分类、关键词和 VIP 延迟只能在该集合内继续筛选。 */
|
||||||
function buildHallCandidateIdsSql(limitPlaceholder: string) {
|
function buildHallCandidateIdsSql(limitPlaceholder: string, categoryLimitsPlaceholder: string) {
|
||||||
return `
|
return `
|
||||||
SELECT candidate.id
|
SELECT ranked.id
|
||||||
FROM work_orders candidate
|
FROM (
|
||||||
LEFT JOIN (
|
SELECT candidate.id, candidate.category_id, candidate.hall_queued_at,
|
||||||
SELECT
|
ROW_NUMBER() OVER (
|
||||||
work_order_id,
|
PARTITION BY candidate.category_id
|
||||||
SUM(quantity) FILTER (WHERE status != 'cancelled') AS active_share_quantity
|
ORDER BY candidate.hall_queued_at ASC NULLS LAST, candidate.id ASC
|
||||||
FROM work_order_shares
|
) AS category_rank
|
||||||
GROUP BY work_order_id
|
FROM work_orders candidate
|
||||||
) candidate_shares ON candidate_shares.work_order_id = candidate.id
|
LEFT JOIN (
|
||||||
WHERE candidate.status = 'open'
|
SELECT
|
||||||
AND (
|
work_order_id,
|
||||||
candidate.sharing_enabled IS NOT TRUE
|
SUM(quantity) FILTER (WHERE status != 'cancelled') AS active_share_quantity
|
||||||
OR COALESCE(candidate_shares.active_share_quantity, 0) < candidate.sharing_total_quantity
|
FROM work_order_shares
|
||||||
)
|
GROUP BY work_order_id
|
||||||
ORDER BY candidate.hall_queued_at ASC NULLS LAST, candidate.id ASC
|
) candidate_shares ON candidate_shares.work_order_id = candidate.id
|
||||||
|
WHERE candidate.status = 'open'
|
||||||
|
AND (
|
||||||
|
candidate.sharing_enabled IS NOT TRUE
|
||||||
|
OR COALESCE(candidate_shares.active_share_quantity, 0) < candidate.sharing_total_quantity
|
||||||
|
)
|
||||||
|
) ranked
|
||||||
|
WHERE ranked.category_rank <= COALESCE(
|
||||||
|
NULLIF(${categoryLimitsPlaceholder}::jsonb ->> ranked.category_id::text, '')::int,
|
||||||
|
${limitPlaceholder}
|
||||||
|
)
|
||||||
|
ORDER BY ranked.hall_queued_at ASC NULLS LAST, ranked.id ASC
|
||||||
LIMIT ${limitPlaceholder}
|
LIMIT ${limitPlaceholder}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@@ -887,7 +903,12 @@ function buildHallCandidateIdsSql(limitPlaceholder: string) {
|
|||||||
/** 抢单操作在事务中复核容量与当前打手的可见延迟,避免通过订单 ID 绕过大厅队列。 */
|
/** 抢单操作在事务中复核容量与当前打手的可见延迟,避免通过订单 ID 绕过大厅队列。 */
|
||||||
export async function isWorkOrderWithinHallCapacityWithClient(
|
export async function isWorkOrderWithinHallCapacityWithClient(
|
||||||
client: PoolClient,
|
client: PoolClient,
|
||||||
input: { workOrderId: number; hallCandidateLimit: number; visibleAfterIso?: string | undefined },
|
input: {
|
||||||
|
workOrderId: number
|
||||||
|
hallCandidateLimit: number
|
||||||
|
hallCategoryLimits?: Record<string, number>
|
||||||
|
visibleAfterIso?: string | undefined
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const result = await client.query<{ visible: boolean }>(
|
const result = await client.query<{ visible: boolean }>(
|
||||||
`
|
`
|
||||||
@@ -895,13 +916,14 @@ export async function isWorkOrderWithinHallCapacityWithClient(
|
|||||||
SELECT 1
|
SELECT 1
|
||||||
FROM work_orders wo
|
FROM work_orders wo
|
||||||
WHERE wo.id = $1
|
WHERE wo.id = $1
|
||||||
AND wo.id IN (${buildHallCandidateIdsSql('$2')})
|
AND wo.id IN (${buildHallCandidateIdsSql('$2', '$3')})
|
||||||
AND ($3::timestamptz IS NULL OR wo.hall_queued_at IS NULL OR wo.hall_queued_at <= $3)
|
AND ($4::timestamptz IS NULL OR wo.hall_queued_at IS NULL OR wo.hall_queued_at <= $4)
|
||||||
) AS visible
|
) AS visible
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
input.workOrderId,
|
input.workOrderId,
|
||||||
input.hallCandidateLimit,
|
input.hallCandidateLimit,
|
||||||
|
JSON.stringify(input.hallCategoryLimits || {}),
|
||||||
input.visibleAfterIso ? input.visibleAfterIso : null,
|
input.visibleAfterIso ? input.visibleAfterIso : null,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export async function joinWorkOrderShare(input: {
|
|||||||
takeRemaining?: boolean
|
takeRemaining?: boolean
|
||||||
maxActiveOrders: number
|
maxActiveOrders: number
|
||||||
hallCandidateLimit?: number
|
hallCandidateLimit?: number
|
||||||
|
hallCategoryLimits?: Record<string, number>
|
||||||
visibleAfterIso?: string
|
visibleAfterIso?: string
|
||||||
now: string
|
now: string
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
@@ -137,6 +138,7 @@ export async function joinWorkOrderShare(input: {
|
|||||||
!(await isWorkOrderWithinHallCapacityWithClient(client, {
|
!(await isWorkOrderWithinHallCapacityWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
hallCandidateLimit: input.hallCandidateLimit,
|
hallCandidateLimit: input.hallCandidateLimit,
|
||||||
|
...(input.hallCategoryLimits ? { hallCategoryLimits: input.hallCategoryLimits } : {}),
|
||||||
visibleAfterIso: input.visibleAfterIso,
|
visibleAfterIso: input.visibleAfterIso,
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
createWorkOrder,
|
createWorkOrder,
|
||||||
createWorkOrderEvent,
|
createWorkOrderEvent,
|
||||||
|
getWorkCategoryById,
|
||||||
listWorkOrdersByPlatformOrderId,
|
listWorkOrdersByPlatformOrderId,
|
||||||
} from '../../repositories/worker-platform/index.js'
|
} from '../../repositories/worker-platform/index.js'
|
||||||
import type { JsonObject } from '../../types/json.js'
|
import type { JsonObject } from '../../types/json.js'
|
||||||
@@ -47,6 +48,18 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
|||||||
: 0
|
: 0
|
||||||
: normalizeAmountFen(payload.requiredDepositAmount, 0)
|
: normalizeAmountFen(payload.requiredDepositAmount, 0)
|
||||||
const paymentFen = input.paymentAmount
|
const paymentFen = input.paymentAmount
|
||||||
|
const requestedCategoryId = Number(payload.categoryId || payload.category_id || 0)
|
||||||
|
let categoryId = defaults.category?.id || null
|
||||||
|
if (requestedCategoryId > 0) {
|
||||||
|
const category = await getWorkCategoryById(requestedCategoryId)
|
||||||
|
if (!category || category.status !== 'active') {
|
||||||
|
throw createHttpError('所选接单分类不存在或已停用', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'mock_work_order_category_invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
categoryId = Number(category.id)
|
||||||
|
}
|
||||||
const materialComplete = payload.materialComplete === true || payload.materialComplete === 'true'
|
const materialComplete = payload.materialComplete === true || payload.materialComplete === 'true'
|
||||||
const fields = normalizeRequirementFields(payload.fields)
|
const fields = normalizeRequirementFields(payload.fields)
|
||||||
const { platformOrderId, productName, shop } = input
|
const { platformOrderId, productName, shop } = input
|
||||||
@@ -92,7 +105,7 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
|||||||
workOrderNo: randomId('WO'),
|
workOrderNo: randomId('WO'),
|
||||||
platformOrderId,
|
platformOrderId,
|
||||||
productName,
|
productName,
|
||||||
categoryId: defaults.category?.id || null,
|
categoryId,
|
||||||
status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL,
|
status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL,
|
||||||
rewardAmount,
|
rewardAmount,
|
||||||
requiredDepositAmount,
|
requiredDepositAmount,
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ export async function publishAdminWorkOrder(workOrderId: number | string, actorN
|
|||||||
workOrderId: Number(workOrder.id),
|
workOrderId: Number(workOrder.id),
|
||||||
statuses: [WORK_ORDER_STATUS.OPEN],
|
statuses: [WORK_ORDER_STATUS.OPEN],
|
||||||
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: hallConfig.categoryMaxVisibleOrders,
|
||||||
excludeFilledSharing: true,
|
excludeFilledSharing: true,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
|
|||||||
const page = normalizePage(query.page)
|
const page = normalizePage(query.page)
|
||||||
const pageSize = normalizePageSize(query.pageSize)
|
const pageSize = normalizePageSize(query.pageSize)
|
||||||
const workOrderId = normalizeOptionalId(query.workOrderId ?? query.work_order_id) || 0
|
const workOrderId = normalizeOptionalId(query.workOrderId ?? query.work_order_id) || 0
|
||||||
|
const categoryId = normalizeOptionalId(query.categoryId ?? query.category_id) || 0
|
||||||
const workerId = normalizeOptionalId(query.workerId ?? query.worker_id) || 0
|
const workerId = normalizeOptionalId(query.workerId ?? query.worker_id) || 0
|
||||||
const workerLevelIds = normalizeStatuses(query.workerLevelIds ?? query.worker_level_ids)
|
const workerLevelIds = normalizeStatuses(query.workerLevelIds ?? query.worker_level_ids)
|
||||||
.map(normalizeOptionalId)
|
.map(normalizeOptionalId)
|
||||||
@@ -43,6 +44,7 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
|
|||||||
const filters = {
|
const filters = {
|
||||||
statuses,
|
statuses,
|
||||||
workOrderId,
|
workOrderId,
|
||||||
|
categoryId,
|
||||||
keyword: String(query.keyword || '').trim(),
|
keyword: String(query.keyword || '').trim(),
|
||||||
workerSharingId: workerId,
|
workerSharingId: workerId,
|
||||||
workerLevelIds,
|
workerLevelIds,
|
||||||
|
|||||||
+44
-25
@@ -5,9 +5,44 @@ import { getWorkerHallConfig, saveWorkerHallConfig } from './worker-hall-config-
|
|||||||
|
|
||||||
export async function getAdminWorkerPlatformSummary() {
|
export async function getAdminWorkerPlatformSummary() {
|
||||||
const hallConfig = getWorkerHallConfig()
|
const hallConfig = getWorkerHallConfig()
|
||||||
const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([
|
const [pendingWorkers, pendingMaterial, openOrders, visibleOpenOrders, inProgressOrders] =
|
||||||
listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }),
|
await Promise.all([
|
||||||
listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.PENDING_MATERIAL] }),
|
listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }),
|
||||||
|
listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.PENDING_MATERIAL] }),
|
||||||
|
listWorkOrders({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
statuses: [WORK_ORDER_STATUS.OPEN],
|
||||||
|
excludeFilledSharing: true,
|
||||||
|
}),
|
||||||
|
listWorkOrders({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
statuses: [WORK_ORDER_STATUS.OPEN],
|
||||||
|
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: hallConfig.categoryMaxVisibleOrders,
|
||||||
|
excludeFilledSharing: true,
|
||||||
|
}),
|
||||||
|
listWorkOrders({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
statuses: [WORK_ORDER_STATUS.IN_PROGRESS],
|
||||||
|
useOperationalStatus: true,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
pendingWorkerCount: pendingWorkers.total,
|
||||||
|
pendingMaterialCount: pendingMaterial.total,
|
||||||
|
openOrderCount: visibleOpenOrders.total,
|
||||||
|
queuedHallOrderCount: Math.max(0, openOrders.total - visibleOpenOrders.total),
|
||||||
|
hallMaxVisibleOrders: hallConfig.maxVisibleOrders,
|
||||||
|
inProgressOrderCount: inProgressOrders.total,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAdminWorkerHallConfig() {
|
||||||
|
const config = getWorkerHallConfig()
|
||||||
|
const [openOrders, visibleOpenOrders] = await Promise.all([
|
||||||
listWorkOrders({
|
listWorkOrders({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
@@ -17,32 +52,16 @@ export async function getAdminWorkerPlatformSummary() {
|
|||||||
listWorkOrders({
|
listWorkOrders({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
statuses: [WORK_ORDER_STATUS.IN_PROGRESS],
|
statuses: [WORK_ORDER_STATUS.OPEN],
|
||||||
useOperationalStatus: true,
|
hallCandidateLimit: config.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: config.categoryMaxVisibleOrders,
|
||||||
|
excludeFilledSharing: true,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
return {
|
|
||||||
pendingWorkerCount: pendingWorkers.total,
|
|
||||||
pendingMaterialCount: pendingMaterial.total,
|
|
||||||
openOrderCount: Math.min(openOrders.total, hallConfig.maxVisibleOrders),
|
|
||||||
queuedHallOrderCount: Math.max(0, openOrders.total - hallConfig.maxVisibleOrders),
|
|
||||||
hallMaxVisibleOrders: hallConfig.maxVisibleOrders,
|
|
||||||
inProgressOrderCount: inProgressOrders.total,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAdminWorkerHallConfig() {
|
|
||||||
const config = getWorkerHallConfig()
|
|
||||||
const openOrders = await listWorkOrders({
|
|
||||||
page: 1,
|
|
||||||
pageSize: 1,
|
|
||||||
statuses: [WORK_ORDER_STATUS.OPEN],
|
|
||||||
excludeFilledSharing: true,
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
visibleOrderCount: Math.min(openOrders.total, config.maxVisibleOrders),
|
visibleOrderCount: visibleOpenOrders.total,
|
||||||
queuedOrderCount: Math.max(0, openOrders.total - config.maxVisibleOrders),
|
queuedOrderCount: Math.max(0, openOrders.total - visibleOpenOrders.total),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export async function grabWorkerHallOrder(workOrderId: number | string, session:
|
|||||||
takeRemaining: true,
|
takeRemaining: true,
|
||||||
maxActiveOrders: permissions.maxActiveOrders,
|
maxActiveOrders: permissions.maxActiveOrders,
|
||||||
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: hallConfig.categoryMaxVisibleOrders,
|
||||||
visibleAfterIso,
|
visibleAfterIso,
|
||||||
now,
|
now,
|
||||||
})
|
})
|
||||||
@@ -91,6 +92,7 @@ export async function grabWorkerHallOrder(workOrderId: number | string, session:
|
|||||||
depositAmount: freezeAmount,
|
depositAmount: freezeAmount,
|
||||||
maxActiveOrders: permissions.maxActiveOrders,
|
maxActiveOrders: permissions.maxActiveOrders,
|
||||||
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: hallConfig.categoryMaxVisibleOrders,
|
||||||
visibleAfterIso,
|
visibleAfterIso,
|
||||||
deadlineAt: resolveWorkOrderDeadlineAt(workOrder, now),
|
deadlineAt: resolveWorkOrderDeadlineAt(workOrder, now),
|
||||||
now,
|
now,
|
||||||
@@ -143,6 +145,7 @@ export async function joinWorkerSharingOrder(
|
|||||||
quantity,
|
quantity,
|
||||||
maxActiveOrders: permissions.maxActiveOrders,
|
maxActiveOrders: permissions.maxActiveOrders,
|
||||||
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: hallConfig.categoryMaxVisibleOrders,
|
||||||
visibleAfterIso,
|
visibleAfterIso,
|
||||||
now,
|
now,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,17 +7,36 @@ import {
|
|||||||
} from './worker-hall-config-service.js'
|
} from './worker-hall-config-service.js'
|
||||||
|
|
||||||
test('大厅配置默认展示 50 个订单', () => {
|
test('大厅配置默认展示 50 个订单', () => {
|
||||||
assert.deepEqual(createDefaultWorkerHallConfig(), { maxVisibleOrders: 50 })
|
assert.deepEqual(createDefaultWorkerHallConfig(), {
|
||||||
assert.deepEqual(normalizeWorkerHallConfig({}), { maxVisibleOrders: 50 })
|
maxVisibleOrders: 50,
|
||||||
|
categoryMaxVisibleOrders: {},
|
||||||
|
})
|
||||||
|
assert.deepEqual(normalizeWorkerHallConfig({}), {
|
||||||
|
maxVisibleOrders: 50,
|
||||||
|
categoryMaxVisibleOrders: {},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('大厅配置只接受 1 到 500 的整数', () => {
|
test('大厅配置只接受 1 到 500 的整数', () => {
|
||||||
assert.deepEqual(normalizeWorkerHallConfig({ maxVisibleOrders: 88 }), { maxVisibleOrders: 88 })
|
assert.deepEqual(normalizeWorkerHallConfig({ maxVisibleOrders: 88 }), {
|
||||||
|
maxVisibleOrders: 88,
|
||||||
|
categoryMaxVisibleOrders: {},
|
||||||
|
})
|
||||||
assert.deepEqual(normalizeWorkerHallConfig({ max_visible_orders: 999 }), {
|
assert.deepEqual(normalizeWorkerHallConfig({ max_visible_orders: 999 }), {
|
||||||
maxVisibleOrders: 500,
|
maxVisibleOrders: 500,
|
||||||
|
categoryMaxVisibleOrders: {},
|
||||||
|
})
|
||||||
|
assert.deepEqual(normalizeWorkerHallConfig({ maxVisibleOrders: 0 }), {
|
||||||
|
maxVisibleOrders: 1,
|
||||||
|
categoryMaxVisibleOrders: {},
|
||||||
})
|
})
|
||||||
assert.deepEqual(normalizeWorkerHallConfig({ maxVisibleOrders: 0 }), { maxVisibleOrders: 1 })
|
|
||||||
assert.deepEqual(normalizeWorkerHallConfig({ maxVisibleOrders: 1.5 }), {
|
assert.deepEqual(normalizeWorkerHallConfig({ maxVisibleOrders: 1.5 }), {
|
||||||
maxVisibleOrders: 50,
|
maxVisibleOrders: 50,
|
||||||
|
categoryMaxVisibleOrders: {},
|
||||||
})
|
})
|
||||||
|
assert.deepEqual(
|
||||||
|
normalizeWorkerHallConfig({ categoryMaxVisibleOrders: { '1': 12, '2': 0, '3': 501 } })
|
||||||
|
.categoryMaxVisibleOrders,
|
||||||
|
{ '1': 12 },
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-sto
|
|||||||
|
|
||||||
export type WorkerHallConfig = {
|
export type WorkerHallConfig = {
|
||||||
maxVisibleOrders: number
|
maxVisibleOrders: number
|
||||||
|
categoryMaxVisibleOrders: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorkerHallConfig(): WorkerHallConfig {
|
export function getWorkerHallConfig(): WorkerHallConfig {
|
||||||
@@ -23,7 +24,7 @@ export async function saveWorkerHallConfig(rawValue: unknown): Promise<WorkerHal
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createDefaultWorkerHallConfig(): WorkerHallConfig {
|
export function createDefaultWorkerHallConfig(): WorkerHallConfig {
|
||||||
return { maxVisibleOrders: 50 }
|
return { maxVisibleOrders: 50, categoryMaxVisibleOrders: {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeWorkerHallConfig(rawValue: unknown): WorkerHallConfig {
|
export function normalizeWorkerHallConfig(rawValue: unknown): WorkerHallConfig {
|
||||||
@@ -31,9 +32,24 @@ export function normalizeWorkerHallConfig(rawValue: unknown): WorkerHallConfig {
|
|||||||
const value = Number(source.maxVisibleOrders ?? source.max_visible_orders)
|
const value = Number(source.maxVisibleOrders ?? source.max_visible_orders)
|
||||||
return {
|
return {
|
||||||
maxVisibleOrders: Number.isInteger(value) ? Math.min(500, Math.max(1, value)) : 50,
|
maxVisibleOrders: Number.isInteger(value) ? Math.min(500, Math.max(1, value)) : 50,
|
||||||
|
categoryMaxVisibleOrders: normalizeCategoryMaxVisibleOrders(
|
||||||
|
source.categoryMaxVisibleOrders ?? source.category_max_visible_orders,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCategoryMaxVisibleOrders(value: unknown): Record<string, number> {
|
||||||
|
if (!isPlainObject(value)) return {}
|
||||||
|
return Object.entries(value).reduce<Record<string, number>>((result, [categoryId, raw]) => {
|
||||||
|
const id = Number(categoryId)
|
||||||
|
const limit = Number(raw)
|
||||||
|
if (Number.isInteger(id) && id > 0 && Number.isInteger(limit) && limit >= 1 && limit <= 500) {
|
||||||
|
result[String(id)] = limit
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
function isPlainObject(value: unknown): value is JsonObject {
|
function isPlainObject(value: unknown): value is JsonObject {
|
||||||
return Object.prototype.toString.call(value) === '[object Object]'
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export async function listWorkerHallOrders(query: JsonObject = {}, session: Work
|
|||||||
keyword: String(query.keyword || '').trim(),
|
keyword: String(query.keyword || '').trim(),
|
||||||
categoryId: categoryId || 0,
|
categoryId: categoryId || 0,
|
||||||
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
hallCandidateLimit: hallConfig.maxVisibleOrders,
|
||||||
|
hallCategoryLimits: hallConfig.categoryMaxVisibleOrders,
|
||||||
visibleAfterIso,
|
visibleAfterIso,
|
||||||
excludeFilledSharing: true,
|
excludeFilledSharing: true,
|
||||||
pinnedFirst: true,
|
pinnedFirst: true,
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { App, Button, Card, Descriptions, Form, InputNumber, Space } from 'antd'
|
import { App, Button, Card, Descriptions, Form, InputNumber, Space, Table, Tag } from 'antd'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { fetchAdminWorkerHallConfig, saveAdminWorkerHallConfig } from '@/services/admin'
|
import {
|
||||||
|
fetchAdminWorkCategories,
|
||||||
|
fetchAdminWorkerHallConfig,
|
||||||
|
saveAdminWorkerHallConfig,
|
||||||
|
} from '@/services/admin'
|
||||||
|
import type { WorkCategory } from '@/types/worker-platform'
|
||||||
|
|
||||||
type HallConfigFormValues = {
|
type HallConfigFormValues = {
|
||||||
maxVisibleOrders: number
|
maxVisibleOrders: number
|
||||||
|
categoryMaxVisibleOrders: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HallConfigPanel() {
|
export default function HallConfigPanel() {
|
||||||
@@ -20,13 +26,25 @@ export default function HallConfigPanel() {
|
|||||||
const config = configQuery.data?.data
|
const config = configQuery.data?.data
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config) form.setFieldsValue({ maxVisibleOrders: config.maxVisibleOrders })
|
if (config) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
maxVisibleOrders: config.maxVisibleOrders,
|
||||||
|
categoryMaxVisibleOrders: config.categoryMaxVisibleOrders || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [config, form])
|
}, [config, form])
|
||||||
|
const categoriesQuery = useQuery({
|
||||||
|
queryKey: ['admin-worker-platform-categories'],
|
||||||
|
queryFn: fetchAdminWorkCategories,
|
||||||
|
})
|
||||||
|
|
||||||
async function saveConfig(values: HallConfigFormValues) {
|
async function saveConfig(values: HallConfigFormValues) {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
await saveAdminWorkerHallConfig({ maxVisibleOrders: Number(values.maxVisibleOrders) })
|
await saveAdminWorkerHallConfig({
|
||||||
|
maxVisibleOrders: Number(values.maxVisibleOrders),
|
||||||
|
categoryMaxVisibleOrders: values.categoryMaxVisibleOrders || {},
|
||||||
|
})
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
queryClient.invalidateQueries({ queryKey: ['admin-worker-hall-config'] }),
|
queryClient.invalidateQueries({ queryKey: ['admin-worker-hall-config'] }),
|
||||||
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-summary'] }),
|
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-summary'] }),
|
||||||
@@ -47,7 +65,7 @@ export default function HallConfigPanel() {
|
|||||||
<Descriptions.Item label="等待入厅">{config?.queuedOrderCount || 0}</Descriptions.Item>
|
<Descriptions.Item label="等待入厅">{config?.queuedOrderCount || 0}</Descriptions.Item>
|
||||||
<Descriptions.Item label="当前上限">{config?.maxVisibleOrders || '-'}</Descriptions.Item>
|
<Descriptions.Item label="当前上限">{config?.maxVisibleOrders || '-'}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
<Form form={form} layout="inline" onFinish={saveConfig}>
|
<Form form={form} layout="vertical" onFinish={saveConfig}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="maxVisibleOrders"
|
name="maxVisibleOrders"
|
||||||
label="大厅订单总数"
|
label="大厅订单总数"
|
||||||
@@ -60,6 +78,41 @@ export default function HallConfigPanel() {
|
|||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<div>
|
||||||
|
<Tag color="blue">分类上限</Tag>
|
||||||
|
<span style={{ marginLeft: 8, color: '#666' }}>
|
||||||
|
未填写的分类沿用大厅总上限;填写后仅限制该分类最多展示数量。
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Table<WorkCategory>
|
||||||
|
rowKey="categoryId"
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
loading={categoriesQuery.isLoading}
|
||||||
|
dataSource={categoriesQuery.data?.data.items || []}
|
||||||
|
columns={[
|
||||||
|
{ title: '分类', dataIndex: 'name' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
render: (_, category) => (
|
||||||
|
<Tag color={category.status === 'active' ? 'green' : 'default'}>
|
||||||
|
{category.status === 'active' ? '启用' : '停用'}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最多展示',
|
||||||
|
render: (_, category) => (
|
||||||
|
<Form.Item
|
||||||
|
name={['categoryMaxVisibleOrders', String(category.categoryId)]}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
>
|
||||||
|
<InputNumber min={1} max={500} precision={0} placeholder="跟随总上限" />
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons'
|
import { PlusOutlined } from '@ant-design/icons'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { App, AutoComplete, Button, Card, Form, Input, InputNumber, Select, Switch } from 'antd'
|
import { App, AutoComplete, Button, Card, Form, Input, InputNumber, Select, Switch } from 'antd'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { createAdminMockWorkOrder, fetchAdminMockCustomShops } from '@/services/admin'
|
import {
|
||||||
|
createAdminMockWorkOrder,
|
||||||
|
fetchAdminMockCustomShops,
|
||||||
|
fetchAdminWorkCategories,
|
||||||
|
} from '@/services/admin'
|
||||||
import { fetchAdminKuaishouIndustryShops } from '@/services/admin/kuaishou-industry'
|
import { fetchAdminKuaishouIndustryShops } from '@/services/admin/kuaishou-industry'
|
||||||
import type { WorkOrderStatistics } from '@/types/worker-platform'
|
import type { WorkOrderStatistics } from '@/types/worker-platform'
|
||||||
import { buildMockWorkOrderShopOptions } from '@/utils/mock-work-order'
|
import { buildMockWorkOrderShopOptions } from '@/utils/mock-work-order'
|
||||||
@@ -69,6 +73,19 @@ export function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise<void
|
|||||||
queryKey: ['admin-mock-custom-shops'],
|
queryKey: ['admin-mock-custom-shops'],
|
||||||
queryFn: fetchAdminMockCustomShops,
|
queryFn: fetchAdminMockCustomShops,
|
||||||
})
|
})
|
||||||
|
const categoriesQuery = useQuery({
|
||||||
|
queryKey: ['admin-worker-platform-categories'],
|
||||||
|
queryFn: fetchAdminWorkCategories,
|
||||||
|
})
|
||||||
|
const activeCategories = (categoriesQuery.data?.data.items || []).filter(
|
||||||
|
(category) => category.status === 'active',
|
||||||
|
)
|
||||||
|
const defaultCategoryId = activeCategories[0]?.categoryId
|
||||||
|
useEffect(() => {
|
||||||
|
if (!form.getFieldValue('categoryId') && defaultCategoryId) {
|
||||||
|
form.setFieldValue('categoryId', defaultCategoryId)
|
||||||
|
}
|
||||||
|
}, [defaultCategoryId, form])
|
||||||
const shopOptions = [
|
const shopOptions = [
|
||||||
...buildMockWorkOrderShopOptions(shopsQuery.data?.data.shops || []),
|
...buildMockWorkOrderShopOptions(shopsQuery.data?.data.shops || []),
|
||||||
...(customShopsQuery.data?.data.items || []).map((name) => ({
|
...(customShopsQuery.data?.data.items || []).map((name) => ({
|
||||||
@@ -81,6 +98,7 @@ export function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise<void
|
|||||||
platformOrderId: string
|
platformOrderId: string
|
||||||
sellerId: string
|
sellerId: string
|
||||||
productName: string
|
productName: string
|
||||||
|
categoryId?: number
|
||||||
rewardAmount: number
|
rewardAmount: number
|
||||||
paymentAmount: number
|
paymentAmount: number
|
||||||
materialComplete?: boolean
|
materialComplete?: boolean
|
||||||
@@ -91,9 +109,11 @@ export function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise<void
|
|||||||
await createAdminMockWorkOrder({
|
await createAdminMockWorkOrder({
|
||||||
...values,
|
...values,
|
||||||
acceptanceMode: values.acceptanceMode || 'standard',
|
acceptanceMode: values.acceptanceMode || 'standard',
|
||||||
|
categoryId: values.categoryId,
|
||||||
})
|
})
|
||||||
message.success('Mock 工单已生成')
|
message.success('Mock 工单已生成')
|
||||||
form.resetFields()
|
form.resetFields()
|
||||||
|
if (defaultCategoryId) form.setFieldValue('categoryId', defaultCategoryId)
|
||||||
// 自定义店铺由后端记忆,刷新后即可在下拉中选到。
|
// 自定义店铺由后端记忆,刷新后即可在下拉中选到。
|
||||||
await queryClient.invalidateQueries({ queryKey: ['admin-mock-custom-shops'] })
|
await queryClient.invalidateQueries({ queryKey: ['admin-mock-custom-shops'] })
|
||||||
await onCreated()
|
await onCreated()
|
||||||
@@ -114,6 +134,22 @@ export function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise<void
|
|||||||
>
|
>
|
||||||
<Input placeholder="请输入订单号" />
|
<Input placeholder="请输入订单号" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
className="worker-order-mock-field-category"
|
||||||
|
label="分类"
|
||||||
|
name="categoryId"
|
||||||
|
rules={[{ required: true, message: '请选择接单分类' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
loading={categoriesQuery.isLoading}
|
||||||
|
options={activeCategories.map((category) => ({
|
||||||
|
value: category.categoryId,
|
||||||
|
label: category.name,
|
||||||
|
}))}
|
||||||
|
placeholder="选择接单分类"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
className="worker-order-mock-field-shop"
|
className="worker-order-mock-field-shop"
|
||||||
label="店铺"
|
label="店铺"
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ export default function WorkOrdersPanel() {
|
|||||||
const [createdRange, setCreatedRange] = useState<[string, string] | null>(null)
|
const [createdRange, setCreatedRange] = useState<[string, string] | null>(null)
|
||||||
const [keywordInput, setKeywordInput] = useState('')
|
const [keywordInput, setKeywordInput] = useState('')
|
||||||
const [workerId, setWorkerId] = useState<number | undefined>()
|
const [workerId, setWorkerId] = useState<number | undefined>()
|
||||||
|
const [categoryId, setCategoryId] = useState<number | undefined>()
|
||||||
const [workerLevelIds, setWorkerLevelIds] = useState<number[]>([])
|
const [workerLevelIds, setWorkerLevelIds] = useState<number[]>([])
|
||||||
const [acceptanceMode, setAcceptanceMode] = useState<string | undefined>()
|
const [acceptanceMode, setAcceptanceMode] = useState<string | undefined>()
|
||||||
const [giftPhase, setGiftPhase] = useState<string | undefined>()
|
const [giftPhase, setGiftPhase] = useState<string | undefined>()
|
||||||
@@ -279,6 +280,7 @@ export default function WorkOrdersPanel() {
|
|||||||
statuses.join(','),
|
statuses.join(','),
|
||||||
keyword,
|
keyword,
|
||||||
workerId,
|
workerId,
|
||||||
|
categoryId,
|
||||||
workerLevelIds.join(','),
|
workerLevelIds.join(','),
|
||||||
targetWorkOrderId,
|
targetWorkOrderId,
|
||||||
acceptanceMode || '',
|
acceptanceMode || '',
|
||||||
@@ -296,6 +298,7 @@ export default function WorkOrdersPanel() {
|
|||||||
acceptanceMode: acceptanceMode || undefined,
|
acceptanceMode: acceptanceMode || undefined,
|
||||||
giftPhase: giftPhase || undefined,
|
giftPhase: giftPhase || undefined,
|
||||||
workOrderId: targetWorkOrderId,
|
workOrderId: targetWorkOrderId,
|
||||||
|
categoryId,
|
||||||
keyword,
|
keyword,
|
||||||
workerId,
|
workerId,
|
||||||
workerLevelIds:
|
workerLevelIds:
|
||||||
@@ -346,6 +349,7 @@ export default function WorkOrdersPanel() {
|
|||||||
statuses,
|
statuses,
|
||||||
keyword,
|
keyword,
|
||||||
workerId,
|
workerId,
|
||||||
|
categoryId,
|
||||||
workerLevelIds,
|
workerLevelIds,
|
||||||
targetWorkOrderId,
|
targetWorkOrderId,
|
||||||
acceptanceMode,
|
acceptanceMode,
|
||||||
@@ -671,6 +675,7 @@ export default function WorkOrdersPanel() {
|
|||||||
setKeyword('')
|
setKeyword('')
|
||||||
setStatuses([])
|
setStatuses([])
|
||||||
setWorkerId(undefined)
|
setWorkerId(undefined)
|
||||||
|
setCategoryId(undefined)
|
||||||
setWorkerLevelIds([])
|
setWorkerLevelIds([])
|
||||||
setAcceptanceMode(undefined)
|
setAcceptanceMode(undefined)
|
||||||
setGiftPhase(undefined)
|
setGiftPhase(undefined)
|
||||||
@@ -1240,6 +1245,23 @@ export default function WorkOrdersPanel() {
|
|||||||
setPage(1)
|
setPage(1)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="label"
|
||||||
|
value={categoryId}
|
||||||
|
placeholder="全部接单分类"
|
||||||
|
style={{ width: 180 }}
|
||||||
|
loading={categoriesQuery.isLoading}
|
||||||
|
options={(categoriesQuery.data?.data.items || []).map((category) => ({
|
||||||
|
value: category.categoryId,
|
||||||
|
label: category.name,
|
||||||
|
}))}
|
||||||
|
onChange={(nextCategoryId) => {
|
||||||
|
setCategoryId(nextCategoryId)
|
||||||
|
setPage(1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{showWorkerLevelFilter ? (
|
{showWorkerLevelFilter ? (
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ export function fetchAdminWorkerHallConfig() {
|
|||||||
return apiGet<WorkerHallConfig>('/api/v1/admin/worker-platform/hall-config')
|
return apiGet<WorkerHallConfig>('/api/v1/admin/worker-platform/hall-config')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveAdminWorkerHallConfig(payload: Pick<WorkerHallConfig, 'maxVisibleOrders'>) {
|
export function saveAdminWorkerHallConfig(
|
||||||
|
payload: Pick<WorkerHallConfig, 'maxVisibleOrders' | 'categoryMaxVisibleOrders'>,
|
||||||
|
) {
|
||||||
return apiPost<WorkerHallConfig>('/api/v1/admin/worker-platform/hall-config', payload)
|
return apiPost<WorkerHallConfig>('/api/v1/admin/worker-platform/hall-config', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,6 +576,7 @@ export function createAdminMockWorkOrder(payload: {
|
|||||||
platformOrderId: string
|
platformOrderId: string
|
||||||
sellerId: string
|
sellerId: string
|
||||||
productName: string
|
productName: string
|
||||||
|
categoryId?: number
|
||||||
rewardAmount: number
|
rewardAmount: number
|
||||||
paymentAmount: number
|
paymentAmount: number
|
||||||
materialComplete?: boolean
|
materialComplete?: boolean
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ export type WorkerAnnouncementConfig = {
|
|||||||
|
|
||||||
export type WorkerHallConfig = {
|
export type WorkerHallConfig = {
|
||||||
maxVisibleOrders: number
|
maxVisibleOrders: number
|
||||||
|
categoryMaxVisibleOrders: Record<string, number>
|
||||||
visibleOrderCount: number
|
visibleOrderCount: number
|
||||||
queuedOrderCount: number
|
queuedOrderCount: number
|
||||||
}
|
}
|
||||||
@@ -494,7 +495,11 @@ export type WorkOrderShare = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type WorkOrderGiftPhase =
|
export type WorkOrderGiftPhase =
|
||||||
'material_required' | 'material_submitted' | 'friend_countdown' | 'gift_ready' | string
|
| 'material_required'
|
||||||
|
| 'material_submitted'
|
||||||
|
| 'friend_countdown'
|
||||||
|
| 'gift_ready'
|
||||||
|
| string
|
||||||
|
|
||||||
/** 好友赠送验收模式的打手本人资料 */
|
/** 好友赠送验收模式的打手本人资料 */
|
||||||
export type BoosterMaterial = {
|
export type BoosterMaterial = {
|
||||||
|
|||||||
Reference in New Issue
Block a user