diff --git a/apps/backend/src/services/cost-center/cost-center-overview-service.ts b/apps/backend/src/services/cost-center/cost-center-overview-service.ts index 3c8f58c4..afc47bf1 100644 --- a/apps/backend/src/services/cost-center/cost-center-overview-service.ts +++ b/apps/backend/src/services/cost-center/cost-center-overview-service.ts @@ -89,12 +89,11 @@ export async function getAdminCostCenterOverview(params: { scope?: string } = {} ( SELECT COUNT(*)::int FROM cost_exceptions ce + JOIN order_items oi ON oi.id = ce.order_item_id WHERE ce.status = 'open' AND ( $1::text IS NULL - OR ce.order_item_id IN ( - SELECT oi.id FROM order_items oi WHERE COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $1 - ) + OR COALESCE(oi.fulfillment_channel, CASE WHEN oi.fulfillment_status = 'archived' THEN 'archived' ELSE 'pending' END) = $1 ) ) AS open_exception_count, ( diff --git a/apps/backend/src/services/cost-center/cost-center.test.ts b/apps/backend/src/services/cost-center/cost-center.test.ts index 56cdba0c..c65bbe97 100644 --- a/apps/backend/src/services/cost-center/cost-center.test.ts +++ b/apps/backend/src/services/cost-center/cost-center.test.ts @@ -298,6 +298,16 @@ test('人工客服筛选排除已有其他主渠道的旧 man 商品', () => { assert.ok(sql.includes('BTRIM(live_channel.internal_sku) = BTRIM(p.canonical_name)')) }) +test('成本中心总览异常统计必须走异常表关联,不能全表扫描订单项子查询', async () => { + const { readFile } = await import('node:fs/promises') + const source = await readFile( + new URL('./cost-center-overview-service.ts', import.meta.url), + 'utf8', + ) + assert.ok(source.includes('JOIN order_items oi ON oi.id = ce.order_item_id')) + assert.ok(!source.includes('ce.order_item_id IN (')) +}) + test('tpv: 商品来源归为第三方卡券', async () => { const { productSourceOf } = await import('./cost-center-mappers.js') const assert = (await import('node:assert/strict')).default diff --git a/apps/frontend/src/pages/admin/cost-center/CostCenterShell.tsx b/apps/frontend/src/pages/admin/cost-center/CostCenterShell.tsx index 42edb0df..9981a5f3 100644 --- a/apps/frontend/src/pages/admin/cost-center/CostCenterShell.tsx +++ b/apps/frontend/src/pages/admin/cost-center/CostCenterShell.tsx @@ -22,7 +22,7 @@ import { Tag, Typography, } from 'antd' -import { useQuery, useQueryClient } from '@tanstack/react-query' +import { keepPreviousData, useIsFetching, useQuery, useQueryClient } from '@tanstack/react-query' import { useEffect } from 'react' import FinanceSubNav from '@/components/admin/FinanceSubNav' @@ -97,6 +97,7 @@ export default function CostCenterShell() { const overviewQuery = useQuery({ queryKey: ['admin-cost-center-overview', scope], queryFn: () => fetchAdminCostCenterOverview({ scope }), + placeholderData: keepPreviousData, }) const optionsQuery = useQuery({ queryKey: ['admin-cost-center-options'], @@ -105,6 +106,9 @@ export default function CostCenterShell() { const overview = overviewQuery.data?.data const options = optionsQuery.data?.data + const costCenterFetching = useIsFetching({ + predicate: (query) => String(query.queryKey[0] ?? '').startsWith('admin-cost-center'), + }) function refreshAll() { // 按前缀失效:新增一个 admin-cost-center-* 查询不需要再回来改这里。 @@ -145,7 +149,7 @@ export default function CostCenterShell() { size="small" icon={} onClick={refreshAll} - loading={overviewQuery.isFetching} + loading={costCenterFetching > 0} > 刷新 @@ -162,7 +166,7 @@ export default function CostCenterShell() { ].map(([label, value]) => ( - + ))} @@ -179,11 +183,13 @@ export default function CostCenterShell() { {item.label} - {Number( - item.key === 'all' - ? (overview?.productTotalCount ?? overview?.productCount ?? 0) - : (overview?.productSourceCounts?.[item.key] ?? 0), - )} + {overview + ? Number( + item.key === 'all' + ? (overview.productTotalCount ?? overview.productCount ?? 0) + : (overview.productSourceCounts?.[item.key] ?? 0), + ) + : '—'} ), diff --git a/apps/frontend/src/pages/admin/cost-center/ProductCatalogPanel.tsx b/apps/frontend/src/pages/admin/cost-center/ProductCatalogPanel.tsx index 48ec4e32..af05be39 100644 --- a/apps/frontend/src/pages/admin/cost-center/ProductCatalogPanel.tsx +++ b/apps/frontend/src/pages/admin/cost-center/ProductCatalogPanel.tsx @@ -34,7 +34,7 @@ import { } from 'antd' import type { TableColumnsType } from 'antd' import PercentTable from '@/components/common/PercentTable' -import { useQuery, useQueryClient } from '@tanstack/react-query' +import { keepPreviousData, useQuery, useQueryClient } from '@tanstack/react-query' import { useEffect, useRef, useState } from 'react' import type { Dayjs } from 'dayjs' @@ -152,6 +152,7 @@ export function ProductCatalogPanel() { page: productPage, pageSize: productPageSize, }), + placeholderData: keepPreviousData, }) const products = productsQuery.data?.data.items || [] @@ -901,7 +902,7 @@ export function ProductCatalogPanel() { size="small" columns={productColumns} dataSource={products} - loading={productsQuery.isLoading} + loading={productsQuery.isFetching} rowClassName={(row) => (row.mappingCount > 0 ? 'product-rule-row-mapped' : '')} scroll={{ x: 1180 }} pagination={buildAdminTablePagination({