feat: 门店详情风险/象限卡片增加偏差指标明细对比

This commit is contained in:
freedakgmail
2026-07-30 09:25:44 +08:00
parent 5a1b21104d
commit e7cadef6bd
+57 -1
View File
@@ -107,6 +107,25 @@ export function StoreDetailPage() {
const quadrant = action?.management_quadrant || ''
const quadrantInfo = quadrantDesc[quadrant]
// 公司均值
const companyAvg = { daily_rev: 20144, bill: 36.8, discount: 20.5, margin: 71.5, member: 15.9, repeat: 37.0, delivery: 36.1, combo: 20.6, items: 4.05 }
// 偏差项明细
const deviations: { label: string; store: number; company: number; unit: string; good: 'high' | 'low' }[] = [
{ label: '日均营收', store: Number(risk?.avg_daily_received) || 0, company: companyAvg.daily_rev, unit: '元', good: 'high' },
{ label: '客单价', store: Number(risk?.avg_bill_value) || 0, company: companyAvg.bill, unit: '元', good: 'high' },
{ label: '优惠率', store: Number(risk?.discount_rate_pct) || 0, company: companyAvg.discount, unit: '%', good: 'low' },
{ label: '毛利率', store: Number(risk?.theoretical_margin_pct) || 0, company: companyAvg.margin, unit: '%', good: 'high' },
{ label: '会员占比', store: Number(risk?.member_bill_share_pct) || 0, company: companyAvg.member, unit: '%', good: 'high' },
{ label: '复购率', store: Number(action?.repeat_rate_pct) || 0, company: companyAvg.repeat, unit: '%', good: 'high' },
{ label: '外卖占比', store: Number(action?.delivery_bill_share_pct) || 0, company: companyAvg.delivery, unit: '%', good: 'low' },
{ label: '成本差异率', store: Number(action?.variance_to_theoretical_pct) || 0, company: 20, unit: '%', good: 'low' },
]
const problemDeviations = deviations.filter(d => {
const diff = d.store - d.company
return d.good === 'high' ? diff < 0 : diff > 0
})
return (
<div className="space-y-4">
<button onClick={() => navigate(-1)} className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">
@@ -135,9 +154,24 @@ export function StoreDetailPage() {
<div className={`rounded-lg border p-3 ${riskInfo.color}`}>
<div className="flex items-center gap-2">
<span className="font-bold">{risk.risk_level}</span>
{risk.primary_issue && <span className="text-xs opacity-80"> {risk.primary_issue}</span>}
{action?.problem_combination && <span className="text-xs opacity-80"> {action.problem_combination}</span>}
</div>
<p className="mt-1 text-sm opacity-90">{riskInfo.desc}</p>
{problemDeviations.length > 0 && (
<div className="mt-2 space-y-1 border-t border-current/20 pt-2">
<p className="text-xs font-medium opacity-80">vs </p>
{problemDeviations.map(d => {
const diff = d.store - d.company
const diffStr = diff > 0 ? `+${diff.toFixed(1)}${d.unit}` : `${diff.toFixed(1)}${d.unit}`
return (
<div key={d.label} className="flex items-center justify-between text-xs">
<span className="opacity-80">{d.label}</span>
<span className="font-medium">{d.store.toFixed(1)}{d.unit} <span className="opacity-60">vs {d.company}{d.unit}</span> <span className="font-bold">{diffStr}</span></span>
</div>
)
})}
</div>
)}
</div>
)}
{quadrantInfo && (
@@ -147,6 +181,23 @@ export function StoreDetailPage() {
{action?.scale_tier && <span className="text-xs opacity-80"> {action.scale_tier}</span>}
</div>
<p className="mt-1 text-sm opacity-90">{quadrantInfo}</p>
<div className="mt-2 space-y-1 border-t border-blue-200/50 pt-2">
<p className="text-xs font-medium opacity-80"></p>
<div className="flex items-center justify-between text-xs">
<span className="opacity-80"></span>
<span className="font-medium">{Number(risk?.avg_daily_received || 0).toFixed(0)} <span className="opacity-60">vs 18642</span></span>
</div>
<div className="flex items-center justify-between text-xs">
<span className="opacity-80"></span>
<span className="font-medium">{Number(risk?.theoretical_margin_pct || 0).toFixed(1)}% <span className="opacity-60">vs 70.0%</span></span>
</div>
{action?.benchmark_score && (
<div className="flex items-center justify-between text-xs">
<span className="opacity-80"></span>
<span className="font-medium">{Number(action.benchmark_score).toFixed(1)}</span>
</div>
)}
</div>
</div>
)}
</div>
@@ -158,6 +209,11 @@ export function StoreDetailPage() {
<span className="font-bold text-sm">{action.action_priority}</span>
{action.problem_combination && <span className="text-sm text-muted-foreground">{action.problem_combination}</span>}
{action.business_type && <span className="text-xs rounded bg-muted px-2 py-0.5">{action.business_type}</span>}
{action?.variance_level && (
<span className={`text-xs rounded px-2 py-0.5 ${action.variance_level.includes('红色') ? 'bg-red-100 text-red-700' : action.variance_level.includes('黄色') ? 'bg-yellow-100 text-yellow-700' : 'bg-green-100 text-green-700'}`}>
{action.variance_to_theoretical_pct}% ({action.variance_level})
</span>
)}
</div>
</div>
)}