diff --git a/client/src/pages/SituationalAwarenessPage.tsx b/client/src/pages/SituationalAwarenessPage.tsx
index 0d079c0..00b075e 100644
--- a/client/src/pages/SituationalAwarenessPage.tsx
+++ b/client/src/pages/SituationalAwarenessPage.tsx
@@ -225,8 +225,8 @@ const SCORE_DIMENSIONS = [
function HealthScoreDetail({ store, onClose, navigate }: { store: any; onClose: () => void; navigate: (path: string) => void }) {
const radarData = SCORE_DIMENSIONS.map(d => ({
dimension: d.label,
- score: Number(store[d.key] || 0),
- fullMark: d.max,
+ score: Math.round(Number(store[d.key] || 0) / d.max * 100),
+ fullMark: 100,
}))
const healthColor = Number(store.health_score) >= 75 ? '#22c55e' : Number(store.health_score) >= 55 ? '#eab308' : '#ef4444'
@@ -255,7 +255,7 @@ function HealthScoreDetail({ store, onClose, navigate }: { store: any; onClose:
-
+
diff --git a/client/src/pages/StoreDetailPage.tsx b/client/src/pages/StoreDetailPage.tsx
index e25cffc..b566cb3 100644
--- a/client/src/pages/StoreDetailPage.tsx
+++ b/client/src/pages/StoreDetailPage.tsx
@@ -8,11 +8,22 @@ import { DataTable } from '@/components/DataTable'
import { Pagination } from '@/components/Pagination'
import { FilterableTable } from '@/components/FilterableTable'
import { LoadingSpinner } from '@/components/LoadingSpinner'
-import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils'
+import { formatCurrency, formatPercent, formatNumber, cn } from '@/lib/utils'
import { ArrowLeft } from 'lucide-react'
import { MonthPicker } from '@/components/MonthPicker'
import { useState } from 'react'
+const SCORE_DIMENSIONS = [
+ { key: 'score_revenue', label: '营收规模', max: 20 },
+ { key: 'score_cost', label: '成本控制', max: 20 },
+ { key: 'score_margin', label: '毛利率', max: 15 },
+ { key: 'score_risk', label: '风险等级', max: 10 },
+ { key: 'score_repeat', label: '复购率', max: 10 },
+ { key: 'score_member', label: '会员占比', max: 10 },
+ { key: 'score_task', label: '任务完成', max: 10 },
+ { key: 'score_bill', label: '客单价', max: 5 },
+]
+
type DetailTab = 'overview' | 'meal' | 'category' | 'cost' | 'member' | 'anomaly' | 'tasks'
const TABS: { key: DetailTab; label: string }[] = [
@@ -37,6 +48,11 @@ export function StoreDetailPage() {
queryFn: () => api.get(`/stores/${code}`),
})
+ const { data: healthData } = useQuery({
+ queryKey: ['store-health', code, month],
+ queryFn: () => api.get('/situational-awareness/health-score', { params: { month } }),
+ })
+
const { data: dailyData } = useQuery({
queryKey: ['store', code, 'daily'],
queryFn: () => api.get(`/stores/${code}/daily`),
@@ -80,6 +96,8 @@ export function StoreDetailPage() {
const sd = (storeData as any)?.data
const daily = ((dailyData as any)?.data) || []
+ const healthRows = ((healthData as any)?.data) || []
+ const health = healthRows.find((r: any) => r.store_code === code)
const tasks = ((tasksData as any)?.data) || []
const mealRows = ((mealData as any)?.data) || []
const cat = (catData as any)?.data
@@ -137,6 +155,18 @@ export function StoreDetailPage() {
{sc.store_name}
+ {health && (
+ <>
+
= 75 ? 'text-green-600' : Number(health.health_score) >= 55 ? 'text-yellow-600' : 'text-red-600')}>
+ {health.health_score}
+
+
{health.health_status}
+ >
+ )}
{risk?.risk_level &&
}
{quadrant &&
{quadrant}}
@@ -234,6 +264,41 @@ export function StoreDetailPage() {
{/* 概览 Tab */}
{tab === 'overview' && (
+ {health && (
+
+
健康度评分维度
+
+
+ ({ dimension: d.label, score: Math.round(Number(health[d.key] || 0) / d.max * 100), fullMark: 100 }))}>
+
+
+
+ = 75 ? '#22c55e' : Number(health.health_score) >= 55 ? '#eab308' : '#ef4444'} fill={Number(health.health_score) >= 75 ? '#22c55e' : Number(health.health_score) >= 55 ? '#eab308' : '#ef4444'} fillOpacity={0.3} />
+
+
+
+
+ {SCORE_DIMENSIONS.map(d => {
+ const score = Number(health[d.key] || 0)
+ const pct = (score / d.max) * 100
+ const color = pct >= 75 ? '#22c55e' : pct >= 50 ? '#eab308' : '#ef4444'
+ return (
+
+
+ {d.label}
+ {score.toFixed(1)} / {d.max}
+
+
+
+ )
+ })}
+
+
+
+ )}
+
日度实收趋势