feat: 统一SearchSelect下拉组件 + 修复营销方案数据 + 平台经济性增强
- 创建SearchSelect搜索下拉组件,选项≤8自动隐藏搜索框 - 全站替换原生select为SearchSelect(13处下拉) - 修复/revenue/store-ranking优惠率字段名不匹配(discount_rate_pct→avg_discount_rate_pct) - RevenuePage门店营收明细改用FilterableTable支持筛选排序分页 - 修复/marketing/plans按门店分组改为按marketing_plan分组 - 营销方案分析补充消费额、优惠额列和Top5概览卡片 - PlatformPage门店平台经济性增加门店搜索、经济性优列(平台分色) - 平台成本率算法说明显示在标题下方
This commit is contained in:
@@ -5,6 +5,7 @@ import api from '@/lib/api'
|
||||
import { MetricCard } from '@/components/MetricCard'
|
||||
import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||||
import { CollapsibleSection } from '@/components/CollapsibleSection'
|
||||
import { FilterableTable } from '@/components/FilterableTable'
|
||||
import { formatNumber, formatCurrency, formatPercent } from '@/lib/utils'
|
||||
import { BarChart3, TrendingDown, Users, Receipt } from 'lucide-react'
|
||||
import { MonthPicker } from '@/components/MonthPicker'
|
||||
@@ -282,44 +283,31 @@ export function RevenuePage() {
|
||||
|
||||
{/* 门店营收明细表 */}
|
||||
<CollapsibleSection title="门店营收明细" subtitle="全部门店营收排名">
|
||||
<div className="overflow-auto" style={{ maxHeight: 400 }}>
|
||||
<table className="w-full text-xs">
|
||||
<thead className="sticky top-0 bg-muted">
|
||||
<tr>
|
||||
<th className="text-left p-2">排名</th>
|
||||
<th className="text-left p-2">门店</th>
|
||||
<th className="text-right p-2">实收</th>
|
||||
<th className="text-right p-2">账单数</th>
|
||||
<th className="text-right p-2">客单价</th>
|
||||
<th className="text-right p-2">优惠率</th>
|
||||
<th className="text-right p-2">客流量</th>
|
||||
<th className="text-right p-2">理论毛利率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rankRows.map((s: any, i: number) => (
|
||||
<tr
|
||||
key={s.store_code}
|
||||
className="cursor-pointer border-b hover:bg-muted/50"
|
||||
onClick={() => navigate(`/stores/${s.store_code}`)}
|
||||
>
|
||||
<td className="p-2 font-bold">{i + 1}</td>
|
||||
<td className="p-2 font-medium">{s.store_name}</td>
|
||||
<td className="text-right p-2">{formatCurrency(s.received)}</td>
|
||||
<td className="text-right p-2">{formatNumber(s.bill_count)}</td>
|
||||
<td className="text-right p-2">{formatCurrency(s.avg_bill_value)}</td>
|
||||
<td className="text-right p-2">
|
||||
<span className={Number(s.avg_discount_rate_pct) > 25 ? 'text-red-600 font-bold' : Number(s.avg_discount_rate_pct) > 20 ? 'text-yellow-600' : ''}>
|
||||
{formatPercent(s.avg_discount_rate_pct)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="text-right p-2">{formatNumber(s.guests)}</td>
|
||||
<td className="text-right p-2">{formatPercent(s.avg_theoretical_margin_pct)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<FilterableTable
|
||||
data={rankRows}
|
||||
filterKey="store_name"
|
||||
filterLabel="全部门店"
|
||||
onRowClick={(r) => r.store_code && navigate(`/stores/${r.store_code}`)}
|
||||
sortOptions={[
|
||||
{ key: 'received', label: '实收' },
|
||||
{ key: 'bill_count', label: '账单数' },
|
||||
{ key: 'avg_bill_value', label: '客单价' },
|
||||
{ key: 'avg_discount_rate_pct', label: '优惠率' },
|
||||
{ key: 'guests', label: '客流量' },
|
||||
{ key: 'avg_theoretical_margin_pct', label: '毛利率' },
|
||||
]}
|
||||
defaultSort="received"
|
||||
defaultOrder="desc"
|
||||
columns={[
|
||||
{ key: 'store_name', label: '门店' },
|
||||
{ key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) },
|
||||
{ key: 'bill_count', label: '账单数', align: 'right', render: (r) => formatNumber(r.bill_count) },
|
||||
{ key: 'avg_bill_value', label: '客单价', align: 'right', render: (r) => formatCurrency(r.avg_bill_value) },
|
||||
{ key: 'avg_discount_rate_pct', label: '优惠率', align: 'right', render: (r) => <span className={Number(r.avg_discount_rate_pct) > 25 ? 'text-red-600 font-bold' : Number(r.avg_discount_rate_pct) > 20 ? 'text-yellow-600' : ''}>{formatPercent(r.avg_discount_rate_pct)}</span> },
|
||||
{ key: 'guests', label: '客流量', align: 'right', render: (r) => formatNumber(r.guests) },
|
||||
{ key: 'avg_theoretical_margin_pct', label: '理论毛利率', align: 'right', render: (r) => formatPercent(r.avg_theoretical_margin_pct) },
|
||||
]}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user