From 737f9ddcbed0d2f06008c3ea70d67dba5731be38 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 16 Aug 2026 15:48:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=8D=95=E5=B7=A5=E5=8D=95=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AD=9B=E9=80=89=E6=94=AF=E6=8C=81=E5=A4=9A=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前端:状态筛选改为多选(Select mode=multiple),可一次筛选多个状态 - 后端:listWorkOrders 支持 statuses 数组查询(ANY 匹配),兼容旧单状态参数 - 批量验收入口仅在单独筛选待验收时显示,避免多选误操作 --- .../src/repositories/worker-platform/types.ts | 3 ++ .../worker-platform/work-order-repo.ts | 20 +++++-- .../services/worker-platform/admin-service.ts | 10 ++-- .../src/services/worker-platform/mappers.ts | 21 ++++++++ .../worker-platform/worker-service.ts | 6 +-- .../pages/admin/panels/WorkOrdersPanel.tsx | 54 ++++++++++++------- 6 files changed, 83 insertions(+), 31 deletions(-) diff --git a/apps/backend/src/repositories/worker-platform/types.ts b/apps/backend/src/repositories/worker-platform/types.ts index bea91be1..97256d99 100644 --- a/apps/backend/src/repositories/worker-platform/types.ts +++ b/apps/backend/src/repositories/worker-platform/types.ts @@ -209,7 +209,10 @@ export type GrabWorkOrderResult = { export type ListInput = { page?: number pageSize?: number + /** 单状态筛选(兼容 worker 用户等其他列表) */ status?: string + /** 支持多个状态同时筛选(空数组表示全部状态) */ + statuses?: string[] keyword?: string workerId?: number categoryId?: number diff --git a/apps/backend/src/repositories/worker-platform/work-order-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-repo.ts index 76ab4c81..e4c94fc7 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-repo.ts @@ -737,6 +737,7 @@ export async function listWorkOrdersByPlatformOrderId( export async function listWorkOrders({ page = 1, pageSize = 20, + statuses = [], status = '', keyword = '', workerId = 0, @@ -745,8 +746,10 @@ export async function listWorkOrders({ visibleAfterIso = '', sort = 'id_desc', }: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> { + const effectiveStatuses = + statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : [] const { whereClause, params } = buildWorkOrderWhere({ - status, + statuses: effectiveStatuses, keyword, workerId, categoryId, @@ -2522,7 +2525,7 @@ async function getWorkOrderByIdWithClient( } function buildWorkOrderWhere({ - status = '', + statuses = [], keyword = '', workerId = 0, categoryId = 0, @@ -2531,9 +2534,16 @@ function buildWorkOrderWhere({ }: ListInput) { const filters: string[] = [] const params: unknown[] = [] - if (status) { - params.push(status) - filters.push(`wo.status = $${params.length}`) + const normalizedStatuses = [ + ...new Set( + statuses + .map((item) => String(item || '').trim()) + .filter(Boolean), + ), + ] + if (normalizedStatuses.length > 0) { + params.push(normalizedStatuses) + filters.push(`wo.status = ANY($${params.length}::text[])`) } if (keyword) { params.push(`%${keyword}%`) diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index f6be33d1..cb124423 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -92,7 +92,7 @@ import { saveWorkerFinanceConfig, } from './worker-finance-config-service.js' -import { DEFAULT_CATEGORY_KEY, DEFAULT_DEPOSIT_THRESHOLD_AMOUNT, DEFAULT_LEVEL_KEY, DEFAULT_LEVEL_NAME, mapFinanceRequest, mapWallet, mapWorkCategory, mapWorkOrderAdmin, mapWorkOrderShare, mapWorkProductRule, mapWorkerLevel, mapWorkerUser, normalizeAdminFinanceReviewStatus, normalizeAmountFen, normalizeBoolean, normalizeEnabledStatus, normalizeFinanceRequestStatus, normalizeFinanceRequestType, normalizeInteger, normalizeMatchType, normalizeOptionalId, normalizePositiveInteger, normalizeProblemResolutionAction, normalizeRequirementFields, normalizeRequirementFieldsFromPayload, normalizeReviewStatus, normalizeSessionVersion, normalizeSlugKey, normalizeSubmittedFields, normalizeUploadedFiles, normalizeWorkerType, resolveFreezeDepositAmount, resolveMatchingProductRule, resolveRequirementFields, resolveSkuNameQuantity, resolveWorkerPermissions } from './mappers.js' +import { DEFAULT_CATEGORY_KEY, DEFAULT_DEPOSIT_THRESHOLD_AMOUNT, DEFAULT_LEVEL_KEY, DEFAULT_LEVEL_NAME, mapFinanceRequest, mapWallet, mapWorkCategory, mapWorkOrderAdmin, mapWorkOrderShare, mapWorkProductRule, mapWorkerLevel, mapWorkerUser, normalizeAdminFinanceReviewStatus, normalizeAmountFen, normalizeBoolean, normalizeEnabledStatus, normalizeFinanceRequestStatus, normalizeFinanceRequestType, normalizeInteger, normalizeMatchType, normalizeOptionalId, normalizePositiveInteger, normalizeProblemResolutionAction, normalizeRequirementFields, normalizeRequirementFieldsFromPayload, normalizeReviewStatus, normalizeSessionVersion, normalizeSlugKey, normalizeStatuses, normalizeSubmittedFields, normalizeUploadedFiles, normalizeWorkerType, resolveFreezeDepositAmount, resolveMatchingProductRule, resolveRequirementFields, resolveSkuNameQuantity, resolveWorkerPermissions } from './mappers.js' import { ensureWorkerPlatformDefaults, getRequiredWorkOrder, getRequiredWorker, normalizeWorkOrderTimeoutPolicy } from './worker-service.js' export async function listAdminWorkerLevels() { @@ -566,7 +566,7 @@ export async function listAdminWorkOrders(query: JsonObject = {}) { const { items, total } = await listWorkOrders({ page, pageSize, - status: String(query.status || '').trim(), + statuses: normalizeStatuses(query.status), keyword: String(query.keyword || '').trim(), workerSharingId: workerId, }) @@ -1321,9 +1321,9 @@ export async function cancelAdminWorkOrder( export async function getAdminWorkerPlatformSummary() { const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([ listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }), - listWorkOrders({ page: 1, pageSize: 1, status: WORK_ORDER_STATUS.PENDING_MATERIAL }), - listWorkOrders({ page: 1, pageSize: 1, status: WORK_ORDER_STATUS.OPEN }), - listWorkOrders({ page: 1, pageSize: 1, status: WORK_ORDER_STATUS.IN_PROGRESS }), + listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.PENDING_MATERIAL] }), + listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.OPEN] }), + listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.IN_PROGRESS] }), ]) return { pendingWorkerCount: pendingWorkers.total, diff --git a/apps/backend/src/services/worker-platform/mappers.ts b/apps/backend/src/services/worker-platform/mappers.ts index 052dcd10..c13a9d0c 100644 --- a/apps/backend/src/services/worker-platform/mappers.ts +++ b/apps/backend/src/services/worker-platform/mappers.ts @@ -758,6 +758,27 @@ export function normalizeStringArray(value: unknown): string[] { return value.map((item) => String(item || '').trim()).filter(Boolean) } +/** + * 将查询参数中的状态过滤解析为去重后的状态数组。 + * 支持:逗号分隔的字符串(如 "open,in_progress")、字符串数组、单个字符串。 + */ +export function normalizeStatuses(value: unknown): string[] { + const rawList: unknown[] = Array.isArray(value) + ? value + : typeof value === 'string' && value.trim() + ? value.split(',') + : value === undefined || value === null + ? [] + : [value] + return [ + ...new Set( + rawList + .map((item) => String(item || '').trim()) + .filter(Boolean), + ), + ] +} + export function normalizeUploadedFiles(value: unknown) { if (!Array.isArray(value)) return [] return value diff --git a/apps/backend/src/services/worker-platform/worker-service.ts b/apps/backend/src/services/worker-platform/worker-service.ts index 723aa67f..e9c3422a 100644 --- a/apps/backend/src/services/worker-platform/worker-service.ts +++ b/apps/backend/src/services/worker-platform/worker-service.ts @@ -101,7 +101,7 @@ import { saveWorkerFinanceConfig, } from './worker-finance-config-service.js' -import { DEFAULT_CATEGORY_KEY, DEFAULT_LEVEL_KEY, DEFAULT_LEVEL_NAME, INVITE_CODE_LENGTH, INVITE_CODE_RETRY_LIMIT, createWorkerSession, ensureWorkerAuthConfigured, hashWorkerPassword, isIgnorableWorkerAuthError, mapCollectLookupOrder, mapFinanceRequest, mapWalletLedger, mapWorkCategory, mapWorkOrderForWorker, mapWorkOrderPublic, mapWorkOrderShare, mapWorkerUser, normalizeAmountFen, normalizeFinanceRequestStatus, normalizeFinanceRequestType, normalizeInteger, normalizeOptionalId, normalizePassword, normalizePositiveInteger, normalizeProofFiles, normalizeSessionVersion, normalizeStringArray, normalizeSubmittedFields, normalizeUploadedFiles, normalizeUsername, normalizeWalletLedgerType, normalizeWithdrawChannel, resolveFreezeDepositAmount, resolveRequirementFields, resolveWorkerPermissions, safeCompare, signWorkerPayload, throwGrabWorkOrderFailure, validateWorkerPassword, validateWorkerUsername, verifyWorkerPassword } from './mappers.js' +import { DEFAULT_CATEGORY_KEY, DEFAULT_LEVEL_KEY, DEFAULT_LEVEL_NAME, INVITE_CODE_LENGTH, INVITE_CODE_RETRY_LIMIT, createWorkerSession, ensureWorkerAuthConfigured, hashWorkerPassword, isIgnorableWorkerAuthError, mapCollectLookupOrder, mapFinanceRequest, mapWalletLedger, mapWorkCategory, mapWorkOrderForWorker, mapWorkOrderPublic, mapWorkOrderShare, mapWorkerUser, normalizeAmountFen, normalizeFinanceRequestStatus, normalizeFinanceRequestType, normalizeInteger, normalizeOptionalId, normalizePassword, normalizePositiveInteger, normalizeProofFiles, normalizeSessionVersion, normalizeStatuses, normalizeStringArray, normalizeSubmittedFields, normalizeUploadedFiles, normalizeUsername, normalizeWalletLedgerType, normalizeWithdrawChannel, resolveFreezeDepositAmount, resolveRequirementFields, resolveWorkerPermissions, safeCompare, signWorkerPayload, throwGrabWorkOrderFailure, validateWorkerPassword, validateWorkerUsername, verifyWorkerPassword } from './mappers.js' export type WorkerSession = { sessionId: string @@ -820,7 +820,7 @@ export async function listWorkerHallOrders(query: JsonObject = {}, session: Work const { items, total } = await listWorkOrders({ page, pageSize, - status: WORK_ORDER_STATUS.OPEN, + statuses: [WORK_ORDER_STATUS.OPEN], keyword: String(query.keyword || '').trim(), categoryId: categoryId || 0, visibleAfterIso, @@ -991,7 +991,7 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker const { items, total } = await listWorkOrders({ page, pageSize, - status: String(query.status || '').trim(), + statuses: normalizeStatuses(query.status), keyword: String(query.keyword || '').trim(), workerSharingId: worker.id, sort: 'worker_claimed_at_desc', diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 2491b353..a751a6ef 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -141,7 +141,7 @@ export default function WorkOrdersPanel() { const navigate = useNavigate() const queryClient = useQueryClient() const canEditRequirementFields = hasAdminRole('operator') - const [status, setStatus] = useState('') + const [statuses, setStatuses] = useState([]) const [keyword, setKeyword] = useState('') const [keywordInput, setKeywordInput] = useState('') const [workerId, setWorkerId] = useState() @@ -181,10 +181,27 @@ export default function WorkOrdersPanel() { unitReward?: number totalAmount?: number }>() + // 仅单独筛选「待验收」时提供批量验收入口(多选状态下不启用勾选,避免误操作) + const isPendingAcceptanceOnly = + statuses.length === 1 && statuses[0] === 'pending_acceptance' const ordersQuery = useQuery({ - queryKey: ['admin-worker-platform-orders', status, keyword, workerId, page, pageSize], - queryFn: () => fetchAdminWorkOrders({ status, keyword, workerId, page, pageSize }), + queryKey: [ + 'admin-worker-platform-orders', + statuses.join(','), + keyword, + workerId, + page, + pageSize, + ], + queryFn: () => + fetchAdminWorkOrders({ + status: statuses.length > 0 ? statuses.join(',') : undefined, + keyword, + workerId, + page, + pageSize, + }), }) const ordersPagination = ordersQuery.data?.data.pagination const categoriesQuery = useQuery({ @@ -208,7 +225,7 @@ export default function WorkOrdersPanel() { useEffect(() => { setSelectedAcceptanceOrderIds([]) - }, [status, keyword, workerId, page, pageSize]) + }, [statuses, keyword, workerId, page, pageSize]) function openAssignModal(row: WorkOrder) { setAssignOrder(row) @@ -441,7 +458,7 @@ export default function WorkOrdersPanel() { function clearOrderFilters() { setKeywordInput('') setKeyword('') - setStatus('') + setStatuses([]) setWorkerId(undefined) setPage(1) } @@ -854,17 +871,18 @@ export default function WorkOrdersPanel() { onPressEnter={searchOrders} />