接单工单状态筛选支持多选

- 前端:状态筛选改为多选(Select mode=multiple),可一次筛选多个状态
- 后端:listWorkOrders 支持 statuses 数组查询(ANY 匹配),兼容旧单状态参数
- 批量验收入口仅在单独筛选待验收时显示,避免多选误操作
This commit is contained in:
yml2213
2026-08-16 15:48:15 +08:00
parent 31f134579c
commit 737f9ddcbe
6 changed files with 83 additions and 31 deletions
@@ -209,7 +209,10 @@ export type GrabWorkOrderResult = {
export type ListInput = { export type ListInput = {
page?: number page?: number
pageSize?: number pageSize?: number
/** 单状态筛选(兼容 worker 用户等其他列表) */
status?: string status?: string
/** 支持多个状态同时筛选(空数组表示全部状态) */
statuses?: string[]
keyword?: string keyword?: string
workerId?: number workerId?: number
categoryId?: number categoryId?: number
@@ -737,6 +737,7 @@ export async function listWorkOrdersByPlatformOrderId(
export async function listWorkOrders({ export async function listWorkOrders({
page = 1, page = 1,
pageSize = 20, pageSize = 20,
statuses = [],
status = '', status = '',
keyword = '', keyword = '',
workerId = 0, workerId = 0,
@@ -745,8 +746,10 @@ export async function listWorkOrders({
visibleAfterIso = '', visibleAfterIso = '',
sort = 'id_desc', sort = 'id_desc',
}: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> { }: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> {
const effectiveStatuses =
statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : []
const { whereClause, params } = buildWorkOrderWhere({ const { whereClause, params } = buildWorkOrderWhere({
status, statuses: effectiveStatuses,
keyword, keyword,
workerId, workerId,
categoryId, categoryId,
@@ -2522,7 +2525,7 @@ async function getWorkOrderByIdWithClient(
} }
function buildWorkOrderWhere({ function buildWorkOrderWhere({
status = '', statuses = [],
keyword = '', keyword = '',
workerId = 0, workerId = 0,
categoryId = 0, categoryId = 0,
@@ -2531,9 +2534,16 @@ function buildWorkOrderWhere({
}: ListInput) { }: ListInput) {
const filters: string[] = [] const filters: string[] = []
const params: unknown[] = [] const params: unknown[] = []
if (status) { const normalizedStatuses = [
params.push(status) ...new Set(
filters.push(`wo.status = $${params.length}`) 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) { if (keyword) {
params.push(`%${keyword}%`) params.push(`%${keyword}%`)
@@ -92,7 +92,7 @@ import {
saveWorkerFinanceConfig, saveWorkerFinanceConfig,
} from './worker-finance-config-service.js' } 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' import { ensureWorkerPlatformDefaults, getRequiredWorkOrder, getRequiredWorker, normalizeWorkOrderTimeoutPolicy } from './worker-service.js'
export async function listAdminWorkerLevels() { export async function listAdminWorkerLevels() {
@@ -566,7 +566,7 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
const { items, total } = await listWorkOrders({ const { items, total } = await listWorkOrders({
page, page,
pageSize, pageSize,
status: String(query.status || '').trim(), statuses: normalizeStatuses(query.status),
keyword: String(query.keyword || '').trim(), keyword: String(query.keyword || '').trim(),
workerSharingId: workerId, workerSharingId: workerId,
}) })
@@ -1321,9 +1321,9 @@ export async function cancelAdminWorkOrder(
export async function getAdminWorkerPlatformSummary() { export async function getAdminWorkerPlatformSummary() {
const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([ const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([
listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }), listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }),
listWorkOrders({ page: 1, pageSize: 1, status: WORK_ORDER_STATUS.PENDING_MATERIAL }), listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.PENDING_MATERIAL] }),
listWorkOrders({ page: 1, pageSize: 1, status: WORK_ORDER_STATUS.OPEN }), listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.OPEN] }),
listWorkOrders({ page: 1, pageSize: 1, status: WORK_ORDER_STATUS.IN_PROGRESS }), listWorkOrders({ page: 1, pageSize: 1, statuses: [WORK_ORDER_STATUS.IN_PROGRESS] }),
]) ])
return { return {
pendingWorkerCount: pendingWorkers.total, pendingWorkerCount: pendingWorkers.total,
@@ -758,6 +758,27 @@ export function normalizeStringArray(value: unknown): string[] {
return value.map((item) => String(item || '').trim()).filter(Boolean) 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) { export function normalizeUploadedFiles(value: unknown) {
if (!Array.isArray(value)) return [] if (!Array.isArray(value)) return []
return value return value
@@ -101,7 +101,7 @@ import {
saveWorkerFinanceConfig, saveWorkerFinanceConfig,
} from './worker-finance-config-service.js' } 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 = { export type WorkerSession = {
sessionId: string sessionId: string
@@ -820,7 +820,7 @@ export async function listWorkerHallOrders(query: JsonObject = {}, session: Work
const { items, total } = await listWorkOrders({ const { items, total } = await listWorkOrders({
page, page,
pageSize, pageSize,
status: WORK_ORDER_STATUS.OPEN, statuses: [WORK_ORDER_STATUS.OPEN],
keyword: String(query.keyword || '').trim(), keyword: String(query.keyword || '').trim(),
categoryId: categoryId || 0, categoryId: categoryId || 0,
visibleAfterIso, visibleAfterIso,
@@ -991,7 +991,7 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker
const { items, total } = await listWorkOrders({ const { items, total } = await listWorkOrders({
page, page,
pageSize, pageSize,
status: String(query.status || '').trim(), statuses: normalizeStatuses(query.status),
keyword: String(query.keyword || '').trim(), keyword: String(query.keyword || '').trim(),
workerSharingId: worker.id, workerSharingId: worker.id,
sort: 'worker_claimed_at_desc', sort: 'worker_claimed_at_desc',
@@ -141,7 +141,7 @@ export default function WorkOrdersPanel() {
const navigate = useNavigate() const navigate = useNavigate()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const canEditRequirementFields = hasAdminRole('operator') const canEditRequirementFields = hasAdminRole('operator')
const [status, setStatus] = useState('') const [statuses, setStatuses] = useState<string[]>([])
const [keyword, setKeyword] = useState('') const [keyword, setKeyword] = useState('')
const [keywordInput, setKeywordInput] = useState('') const [keywordInput, setKeywordInput] = useState('')
const [workerId, setWorkerId] = useState<number | undefined>() const [workerId, setWorkerId] = useState<number | undefined>()
@@ -181,10 +181,27 @@ export default function WorkOrdersPanel() {
unitReward?: number unitReward?: number
totalAmount?: number totalAmount?: number
}>() }>()
// 仅单独筛选「待验收」时提供批量验收入口(多选状态下不启用勾选,避免误操作)
const isPendingAcceptanceOnly =
statuses.length === 1 && statuses[0] === 'pending_acceptance'
const ordersQuery = useQuery({ const ordersQuery = useQuery({
queryKey: ['admin-worker-platform-orders', status, keyword, workerId, page, pageSize], queryKey: [
queryFn: () => fetchAdminWorkOrders({ status, keyword, workerId, page, pageSize }), '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 ordersPagination = ordersQuery.data?.data.pagination
const categoriesQuery = useQuery({ const categoriesQuery = useQuery({
@@ -208,7 +225,7 @@ export default function WorkOrdersPanel() {
useEffect(() => { useEffect(() => {
setSelectedAcceptanceOrderIds([]) setSelectedAcceptanceOrderIds([])
}, [status, keyword, workerId, page, pageSize]) }, [statuses, keyword, workerId, page, pageSize])
function openAssignModal(row: WorkOrder) { function openAssignModal(row: WorkOrder) {
setAssignOrder(row) setAssignOrder(row)
@@ -441,7 +458,7 @@ export default function WorkOrdersPanel() {
function clearOrderFilters() { function clearOrderFilters() {
setKeywordInput('') setKeywordInput('')
setKeyword('') setKeyword('')
setStatus('') setStatuses([])
setWorkerId(undefined) setWorkerId(undefined)
setPage(1) setPage(1)
} }
@@ -854,17 +871,18 @@ export default function WorkOrdersPanel() {
onPressEnter={searchOrders} onPressEnter={searchOrders}
/> />
<Select <Select
value={status} mode="multiple"
style={{ width: 150 }} allowClear
options={[ value={statuses}
{ value: '', label: '全部状态' }, placeholder="全部状态(可多选)"
...FILTERABLE_WORK_ORDER_STATUSES.map((item) => ({ style={{ minWidth: 220, maxWidth: 420 }}
value: item, maxTagCount="responsive"
label: <Tag color={resolveStatusColor(item)}>{formatStatus(item)}</Tag>, options={FILTERABLE_WORK_ORDER_STATUSES.map((item) => ({
})), value: item,
]} label: <Tag color={resolveStatusColor(item)}>{formatStatus(item)}</Tag>,
onChange={(nextStatus) => { }))}
setStatus(nextStatus) onChange={(nextStatuses: string[]) => {
setStatuses(nextStatuses)
setPage(1) setPage(1)
}} }}
/> />
@@ -903,7 +921,7 @@ export default function WorkOrdersPanel() {
title="接单工单" title="接单工单"
extra={ extra={
<Space> <Space>
{status === 'pending_acceptance' ? ( {isPendingAcceptanceOnly ? (
<Button <Button
type="primary" type="primary"
icon={<CheckOutlined />} icon={<CheckOutlined />}
@@ -930,7 +948,7 @@ export default function WorkOrdersPanel() {
dataSource={ordersQuery.data?.data.items || []} dataSource={ordersQuery.data?.data.items || []}
columns={columns} columns={columns}
rowSelection={ rowSelection={
status === 'pending_acceptance' isPendingAcceptanceOnly
? { ? {
selectedRowKeys: selectedAcceptanceOrderIds, selectedRowKeys: selectedAcceptanceOrderIds,
onChange: (selectedRowKeys) => onChange: (selectedRowKeys) =>