perf: Phase 12 — 物化剩余9个慢函数,全量API优化完成

新增物化视图:
- mv_store_action_priority_deep_monthly (10.7s→3ms)
- mv_inventory_cost_classified_monthly (37ms→3ms, 消除重复调用)
- mv_dish_pair_summary_monthly (171s→3ms)
- mv_store_area_efficiency_monthly (10.1s→3ms)
- mv_store_site_profile_monthly (9.3s→3ms)
- mv_site_segment_benchmark_monthly (12s→3ms)
- mv_store_site_replication_monthly (12.6s→3ms)
- mv_store_overlap_risk_monthly (88s→3ms)
- mv_district_site_benchmark_monthly (56s→3ms)

后端: 所有 fn_ 函数调用替换为物化视图查询
前端: BossPage 添加 MonthPicker 组件并传递 month 参数
修复: cost/category-benchmark 排序列名 store_code→sales_store_code
总计: 21个物化视图, 全部API响应<1.7秒
This commit is contained in:
freedakgmail
2026-08-01 14:51:24 +08:00
parent 25db0f3854
commit 122290e3b0
8 changed files with 773 additions and 39 deletions
+30 -25
View File
@@ -8,6 +8,7 @@ import { LoadingSpinner } from '@/components/LoadingSpinner'
import { CollapsibleSection } from '@/components/CollapsibleSection'
import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils'
import { Crown, TrendingDown, AlertTriangle, TrendingUp, Building2, Receipt, Target, ChevronDown } from 'lucide-react'
import { MonthPicker } from '@/components/MonthPicker'
const RISK_COLORS: Record<string, string> = { '红色': '#ef4444', '黄色': '#eab308', '绿色': '#22c55e' }
@@ -64,46 +65,47 @@ function ProfitOppItem({ index, o, opp, pct, confColor }: { index: number; o: an
export function BossPage() {
const navigate = useNavigate()
const [month, setMonth] = useState('2026-04')
const { data: overview, isLoading: odLoading } = useQuery({
queryKey: ['overview'],
queryFn: () => api.get('/overview'),
queryKey: ['overview', month],
queryFn: () => api.get('/overview', { params: { month } }),
})
const { data: daily, isLoading: dailyLoading } = useQuery({
queryKey: ['overview/daily'],
queryFn: () => api.get('/overview/daily'),
queryKey: ['overview/daily', month],
queryFn: () => api.get('/overview/daily', { params: { month } }),
})
const { data: expenseData, isLoading: exLoading } = useQuery({
queryKey: ['store-expense/overview'],
queryFn: () => api.get('/store-expense/overview'),
queryKey: ['store-expense/overview', month],
queryFn: () => api.get('/store-expense/overview', { params: { month } }),
})
const { data: waterfallData, isLoading: wfLoading } = useQuery({
queryKey: ['overview/profit-waterfall'],
queryFn: () => api.get('/overview/profit-waterfall'),
queryKey: ['overview/profit-waterfall', month],
queryFn: () => api.get('/overview/profit-waterfall', { params: { month } }),
})
const { data: riskData, isLoading: riskLoading } = useQuery({
queryKey: ['stores/risk'],
queryFn: () => api.get('/stores/risk'),
queryKey: ['stores/risk', month],
queryFn: () => api.get('/stores/risk', { params: { month } }),
})
const { data: priorityData, isLoading: priorityLoading } = useQuery({
queryKey: ['stores/priority'],
queryFn: () => api.get('/stores/priority'),
queryKey: ['stores/priority', month],
queryFn: () => api.get('/stores/priority', { params: { month } }),
})
const { data: costOverview, isLoading: costLoading } = useQuery({
queryKey: ['cost-analysis/store-overview'],
queryFn: () => api.get('/cost-analysis/store-overview'),
queryKey: ['cost-analysis/store-overview', month],
queryFn: () => api.get('/cost-analysis/store-overview', { params: { month } }),
})
const { data: expenseStructure, isLoading: structLoading } = useQuery({
queryKey: ['store-expense/expense-structure'],
queryFn: () => api.get('/store-expense/expense-structure'),
queryKey: ['store-expense/expense-structure', month],
queryFn: () => api.get('/store-expense/expense-structure', { params: { month } }),
})
const { data: profitRankingData } = useQuery({
queryKey: ['overview/store-profit-ranking'],
queryFn: () => api.get('/overview/store-profit-ranking'),
queryKey: ['overview/store-profit-ranking', month],
queryFn: () => api.get('/overview/store-profit-ranking', { params: { month } }),
})
const { data: profitOppData } = useQuery({
queryKey: ['overview/profit-opportunity'],
queryFn: () => api.get('/overview/profit-opportunity'),
queryKey: ['overview/profit-opportunity', month],
queryFn: () => api.get('/overview/profit-opportunity', { params: { month } }),
})
const pageLoading = odLoading || dailyLoading || exLoading || wfLoading || riskLoading || priorityLoading
@@ -203,12 +205,15 @@ export function BossPage() {
return (
<div className="space-y-4">
{/* 标题 */}
<div className="flex items-center gap-2">
<Crown className="text-yellow-500" size={24} />
<div>
<h1 className="text-xl font-bold"></h1>
<p className="mt-0.5 text-xs text-muted-foreground"> · 20264</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Crown className="text-yellow-500" size={24} />
<div>
<h1 className="text-xl font-bold"></h1>
<p className="mt-0.5 text-xs text-muted-foreground"> · {month}</p>
</div>
</div>
<MonthPicker month={month} onChange={setMonth} />
</div>
{/* ① 核心经营指标 */}