初始化:连锁餐饮数字化运营管理平台
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
|
||||
import api from '@/lib/api'
|
||||
import { MetricCard } from '@/components/MetricCard'
|
||||
import { Badge } from '@/components/Badge'
|
||||
import { DataTable } from '@/components/DataTable'
|
||||
import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
|
||||
export function StoreDetailPage() {
|
||||
const { code } = useParams()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { data: storeData } = useQuery({
|
||||
queryKey: ['store', code],
|
||||
queryFn: () => api.get(`/stores/${code}`),
|
||||
})
|
||||
|
||||
const { data: dailyData } = useQuery({
|
||||
queryKey: ['store', code, 'daily'],
|
||||
queryFn: () => api.get(`/stores/${code}/daily`),
|
||||
})
|
||||
|
||||
const { data: tasksData } = useQuery({
|
||||
queryKey: ['store', code, 'tasks'],
|
||||
queryFn: () => api.get('/tasks', { params: { store_code: code, month: '2026-05', page_size: 50 } }),
|
||||
})
|
||||
|
||||
const sd = (storeData as any)?.data
|
||||
const daily = ((dailyData as any)?.data) || []
|
||||
const tasks = ((tasksData as any)?.data) || []
|
||||
|
||||
if (!sd) return <div>加载中...</div>
|
||||
const sc = sd.scorecard
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<button onClick={() => navigate(-1)} className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">
|
||||
<ArrowLeft size={16} /> 返回
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-xl font-bold">{sc.store_name}</h1>
|
||||
{sd.risk && <Badge type="risk" text={sd.risk.risk_level} />}
|
||||
</div>
|
||||
|
||||
{/* 核心指标 */}
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-6">
|
||||
<MetricCard title="实收" value={sc.received} format="currency" />
|
||||
<MetricCard title="账单数" value={sc.bill_count} format="number" />
|
||||
<MetricCard title="客单价" value={sc.avg_bill_value} format="currency" />
|
||||
<MetricCard title="优惠率" value={sc.discount_rate_pct} format="percent" />
|
||||
<MetricCard title="理论毛利率" value={sc.theoretical_margin_pct} format="percent" />
|
||||
<MetricCard title="会员占比" value={sc.member_bill_share_pct} format="percent" />
|
||||
</div>
|
||||
|
||||
{/* 日度趋势 */}
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h2 className="mb-3 text-sm font-bold">日度实收趋势</h2>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<LineChart data={daily}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="business_date" tick={{ fontSize: 11 }} />
|
||||
<YAxis tick={{ fontSize: 11 }} />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="received" stroke="#3b82f6" name="实收" />
|
||||
<Line type="monotone" dataKey="avg_bill_value" stroke="#eab308" name="客单价" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{/* 平台经济性 */}
|
||||
{sd.platform && (
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h2 className="mb-3 text-sm font-bold">平台经济性</h2>
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
|
||||
<MetricCard title="美团实收" value={sd.platform.meituan_received} format="currency" />
|
||||
<MetricCard title="饿了么实收" value={sd.platform.eleme_received} format="currency" />
|
||||
<MetricCard title="抖音实收" value={sd.platform.douyin_received} format="currency" />
|
||||
<MetricCard title="平台加权成本率" value={sd.platform.weighted_cost_rate_pct} format="percent" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 任务列表 */}
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h2 className="mb-3 text-sm font-bold">门店任务 ({tasks.length})</h2>
|
||||
<DataTable
|
||||
columns={[
|
||||
{ key: 'problem_indicator', label: '问题指标' },
|
||||
{ key: 'priority', label: '优先级', render: (r) => <Badge type="priority" text={r.priority} /> },
|
||||
{ key: 'action_required', label: '行动要求', render: (r) => <span className="text-xs">{r.action_required}</span> },
|
||||
{ key: 'status', label: '状态', render: (r) => <Badge type="status" text={r.status} /> },
|
||||
{ key: 'deadline', label: '截止日', render: (r) => r.deadline?.substring(0, 10) },
|
||||
]}
|
||||
data={tasks}
|
||||
onRowClick={(r) => navigate(`/tasks/${r.task_id}`)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user