diff --git a/apps/backend/src/repositories/worker-platform/types.ts b/apps/backend/src/repositories/worker-platform/types.ts index 9b38d1a6..cb8b90c1 100644 --- a/apps/backend/src/repositories/worker-platform/types.ts +++ b/apps/backend/src/repositories/worker-platform/types.ts @@ -414,6 +414,9 @@ export type ListInput = { sort?: 'id_desc' | 'worker_claimed_at_desc' /** 后台使用拼单聚合业务状态;大厅和打手列表使用原始生命周期。 */ useOperationalStatus?: boolean + /** 创建时间区间(YYYY-MM-DD,上海时区,含起止日);后台列表筛选使用。 */ + createdFrom?: string + createdTo?: string } export type WorkOrderStatistics = { @@ -465,6 +468,9 @@ export type FinanceRequestListInput = { status?: string requestType?: string keyword?: string + /** 申请时间区间(YYYY-MM-DD,上海时区,含起止日);后台列表筛选使用。 */ + createdFrom?: string + createdTo?: string } export type CreateWorkerInput = { diff --git a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts index be357eb6..628a94a3 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts @@ -170,6 +170,8 @@ export async function listWorkOrders({ useOperationalStatus = false, pinnedFirst = false, sort = 'id_desc', + createdFrom = '', + createdTo = '', }: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> { const effectiveStatuses = statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : [] const { joins, whereClause, params } = buildWorkOrderWhere({ @@ -185,6 +187,8 @@ export async function listWorkOrders({ excludeFilledSharing, vipEvidencePending, useOperationalStatus, + createdFrom, + createdTo, }) const totalResult = await query<{ total: number }>( ` @@ -519,10 +523,24 @@ export function buildWorkOrderWhere({ excludeFilledSharing = false, vipEvidencePending = false, useOperationalStatus = false, + createdFrom = '', + createdTo = '', }: ListInput) { const filters: string[] = [] const params: unknown[] = [] const joins: string[] = [] + if (createdFrom) { + params.push(createdFrom) + filters.push( + `wo.created_at >= ($${params.length}::date)::timestamp AT TIME ZONE 'Asia/Shanghai'`, + ) + } + if (createdTo) { + params.push(createdTo) + filters.push( + `wo.created_at < (($${params.length}::date + 1)::timestamp AT TIME ZONE 'Asia/Shanghai')`, + ) + } let workerSharePlaceholder = '' if (excludeFilledSharing) { diff --git a/apps/backend/src/repositories/worker-platform/worker-finance-repo.ts b/apps/backend/src/repositories/worker-platform/worker-finance-repo.ts index 34f97822..c2fbefe1 100644 --- a/apps/backend/src/repositories/worker-platform/worker-finance-repo.ts +++ b/apps/backend/src/repositories/worker-platform/worker-finance-repo.ts @@ -203,6 +203,8 @@ export async function listWorkerFinanceRequests({ status = '', requestType = '', keyword = '', + createdFrom = '', + createdTo = '', }: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> { const { whereClause, params } = buildWorkerFinanceRequestWhere({ requestId, @@ -210,6 +212,8 @@ export async function listWorkerFinanceRequests({ status, requestType, keyword, + createdFrom, + createdTo, }) const totalResult = await query<{ total: number }>( `SELECT COUNT(*)::int AS total FROM worker_finance_requests wfr @@ -415,9 +419,23 @@ function buildWorkerFinanceRequestWhere({ status = '', requestType = '', keyword = '', + createdFrom = '', + createdTo = '', }: FinanceRequestListInput) { const filters: string[] = [] const params: unknown[] = [] + if (createdFrom) { + params.push(createdFrom) + filters.push( + `wfr.created_at >= ($${params.length}::date)::timestamp AT TIME ZONE 'Asia/Shanghai'`, + ) + } + if (createdTo) { + params.push(createdTo) + filters.push( + `wfr.created_at < (($${params.length}::date + 1)::timestamp AT TIME ZONE 'Asia/Shanghai')`, + ) + } if (requestId) { params.push(requestId) filters.push(`wfr.id = $${params.length}`) diff --git a/apps/backend/src/services/admin/admin-query-utils.ts b/apps/backend/src/services/admin/admin-query-utils.ts index 71a3df6d..b7f21955 100644 --- a/apps/backend/src/services/admin/admin-query-utils.ts +++ b/apps/backend/src/services/admin/admin-query-utils.ts @@ -26,6 +26,12 @@ export function normalizeDateQuery(rawValue: unknown, endOfDay = false) { return normalized } +/** 校验并归一化 YYYY-MM-DD 日期参数,非法或空值返回空字符串。 */ +export function normalizeDateString(rawValue: unknown) { + const normalized = String(rawValue || '').trim() + return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : '' +} + export function safeParseJson(rawText: unknown): JsonObject { if (rawText && typeof rawText === 'object') { return rawText diff --git a/apps/backend/src/services/worker-platform/admin-work-order-query-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-query-service.ts index 5a92952e..0d7b4be9 100644 --- a/apps/backend/src/services/worker-platform/admin-work-order-query-service.ts +++ b/apps/backend/src/services/worker-platform/admin-work-order-query-service.ts @@ -6,7 +6,11 @@ import { sumPendingUnfreezeByOrderIds, } from '../../repositories/worker-platform/index.js' import type { JsonObject } from '../../types/json.js' -import { normalizePage, normalizePageSize } from '../admin/admin-query-utils.js' +import { + normalizeDateString, + normalizePage, + normalizePageSize, +} from '../admin/admin-query-utils.js' import { mapWorkOrderAdmin, mapWorkOrderEvents, @@ -32,6 +36,8 @@ export async function listAdminWorkOrders(query: JsonObject = {}) { .filter((levelId): levelId is number => levelId !== null) const statuses = normalizeStatuses(query.status) const vipEvidencePending = normalizeBoolean(query.vipEvidencePending, false) + const createdFrom = normalizeDateString(query.createdFrom) + const createdTo = normalizeDateString(query.createdTo) const filters = { statuses, workOrderId, @@ -40,6 +46,8 @@ export async function listAdminWorkOrders(query: JsonObject = {}) { workerLevelIds, vipEvidencePending, useOperationalStatus: true, + ...(createdFrom ? { createdFrom } : {}), + ...(createdTo ? { createdTo } : {}), } const { items, total } = await listWorkOrders({ page, pageSize, ...filters }) const statistics = await getWorkOrderStatistics(filters) diff --git a/apps/backend/src/services/worker-platform/admin-worker-finance-service.ts b/apps/backend/src/services/worker-platform/admin-worker-finance-service.ts index 0a4ea545..2a0136ff 100644 --- a/apps/backend/src/services/worker-platform/admin-worker-finance-service.ts +++ b/apps/backend/src/services/worker-platform/admin-worker-finance-service.ts @@ -10,7 +10,12 @@ import type { WorkerWithdrawalAccountRow } from '../../repositories/worker-platf import type { JsonObject } from '../../types/json.js' import { createHttpError } from '../../utils/http.js' import { nowIso } from '../../utils/time.js' -import { normalizePage, normalizePageSize, safeParseJson } from '../admin/admin-query-utils.js' +import { + normalizeDateString, + normalizePage, + normalizePageSize, + safeParseJson, +} from '../admin/admin-query-utils.js' import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js' import { refreshUploadedFileUrls } from '../file-storage/file-storage-service.js' import { @@ -162,6 +167,8 @@ export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) { const status = normalizeFinanceRequestStatus(query.status) const requestType = normalizeFinanceRequestType(query.requestType) const keyword = String(query.keyword || '').trim() + const createdFrom = normalizeDateString(query.createdFrom) + const createdTo = normalizeDateString(query.createdTo) const { items, total } = await listWorkerFinanceRequests({ page, pageSize, @@ -169,6 +176,8 @@ export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) { status, requestType, keyword, + ...(createdFrom ? { createdFrom } : {}), + ...(createdTo ? { createdTo } : {}), }) return { items: items.map(mapFinanceRequest), diff --git a/apps/frontend/src/pages/admin/panels/FinancePanel.tsx b/apps/frontend/src/pages/admin/panels/FinancePanel.tsx index 31960b7c..38cadee8 100644 --- a/apps/frontend/src/pages/admin/panels/FinancePanel.tsx +++ b/apps/frontend/src/pages/admin/panels/FinancePanel.tsx @@ -34,6 +34,8 @@ import type { WorkerFinanceConfig, WorkerFinanceRequest, } from '@/types/worker-platform' +import { AdminRangePicker } from '@/components/admin/AdminDatePicker' +import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import { formatAdminDateTime } from '@/utils/admin-time' import { asRecord, formatMoney } from './shared' @@ -69,6 +71,7 @@ export default function FinancePanel() { const [requestType, setRequestType] = useState('') const [keywordInput, setKeywordInput] = useState('') const [keyword, setKeyword] = useState('') + const [createdRange, setCreatedRange] = useState<[string, string] | null>(null) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE) const [savingConfig, setSavingConfig] = useState(false) @@ -109,6 +112,8 @@ export default function FinancePanel() { keyword, page, pageSize, + createdRange ? createdRange[0] : '', + createdRange ? createdRange[1] : '', ], queryFn: () => fetchAdminWorkerFinanceRequests({ @@ -118,6 +123,7 @@ export default function FinancePanel() { keyword, page, pageSize, + ...(createdRange ? { createdFrom: createdRange[0], createdTo: createdRange[1] } : {}), }), }) const financeRequestsPagination = financeRequestsQuery.data?.data.pagination @@ -466,6 +472,18 @@ export default function FinancePanel() { setPage(1) }} /> + { + const [start, end] = dates || [] + setCreatedRange( + start && end + ? [start.format(ADMIN_DATE_FORMAT), end.format(ADMIN_DATE_FORMAT)] + : null, + ) + setPage(1) + }} + />