c2a9e27e49
文档: - 01-项目概述与架构: 技术架构、数据流、部署拓扑 - 02-数据接入与治理: 原始数据导入、物化视图、门店名映射 - 03-指标体系与API开发: 指标分层、SQL模式、常见陷阱 - 04-前端页面开发: 组件规范、页面模板、月份参数管理 - 05-部署与运维: 部署脚本、FRP隧道、PM2、备份 - 06-调试排查手册: 问题分类、8个实际案例、工具速查 - 07-通用方法论: 核心原则、实施阶段、快速复制Checklist 修复: - situational-awareness.ts: salary_month→salary_period, 日期格式改中文 - 客流-人力匹配: 改用attendance_records打卡数据解析在岗人数 - 客流数据除以30天对齐日均
3.8 KiB
3.8 KiB
04 · 前端页面开发
1. 技术栈
- React 18 + TypeScript
- Vite 构建
- @tanstack/react-query 数据请求
- Recharts 图表
- 自研UI组件库(无第三方UI框架)
2. 组件规范
2.1 核心组件
| 组件 | 用途 | 关键Props |
|---|---|---|
MetricCard |
指标卡 | label, value, format: 'number'|'percent'|'currency' |
FilterableTable |
可筛选排序表格 | columns, filterKey, sortOptions, data |
CollapsibleSection |
可折叠区域 | title, subtitle, defaultOpen |
MonthPicker |
月份选择器 | month, onChange |
LoadingSpinner |
加载动画 | text |
2.2 页面结构模板
export default function XxxPage() {
const { month, setMonth } = useMonthParam()
return (
<div className="space-y-6">
{/* 1. 顶部标题 + MonthPicker */}
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">页面标题</h1>
<MonthPicker month={month} onChange={setMonth} />
</div>
{/* 2. MetricCard 概览行 */}
<div className="grid grid-cols-4 gap-4">
<MetricCard label="指标1" value={data?.metric1} format="currency" />
<MetricCard label="指标2" value={data?.metric2} format="number" />
</div>
{/* 3. CollapsibleSection 包裹图表 */}
<CollapsibleSection title="图表区" defaultOpen>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data?.chart}>
<XAxis dataKey="name" />
<YAxis />
<Bar dataKey="value" />
</BarChart>
</ResponsiveContainer>
</CollapsibleSection>
{/* 4. FilterableTable 展示明细 */}
<FilterableTable
columns={columns}
data={data?.list || []}
filterKey="store_name"
sortOptions={[{ key: 'received', label: '按实收' }]}
/>
</div>
)
}
3. 月份参数管理
3.1 统一Hook
// client/src/lib/useMonthParam.ts
export const DEFAULT_MONTH = '2026-04'
export function useMonthParam() {
const [month, setMonth] = useState(DEFAULT_MONTH)
// 同步URL参数
useEffect(() => {
const params = new URLSearchParams(location.search)
const m = params.get('month')
if (m) setMonth(m)
}, [])
return { month, setMonth }
}
3.2 关键原则
- 所有页面统一使用
useMonthParam,不各自new Date().toISOString().slice(0,7) - 默认月份设为有数据的最新月份,避免空白页面
- 修改默认月份只需改
DEFAULT_MONTH一处
4. 数据请求模式
const { data, isLoading } = useQuery({
queryKey: ['xxx-data', month],
queryFn: () => api.get('/api/xxx', { params: { month } }),
staleTime: 5 * 60 * 1000, // 5分钟缓存
})
5. 数据展示约定
5.1 日期格式
后端返回ISO格式 2026-04-29T16:00:00.000Z,前端必须截取:
{new Date(item.business_date).toISOString().substring(0, 10)}
5.2 金额格式化
formatCurrency(1234567.89) // ¥1,234,567.89
formatNumber(12345) // 12,345
formatPercent(85.5) // 85.5%
5.3 空状态处理
{isLoading ? <LoadingSpinner text="加载中..." /> :
data?.length === 0 ? <div className="text-center py-8 text-gray-400">暂无数据</div> :
<FilterableTable data={data} ... />}
6. 典型参考页面
| 页面 | 文件 | 特点 |
|---|---|---|
| 老板驾驶舱 | BossPage.tsx |
多Tab + MetricCard + 图表组合 |
| 会员复购 | MemberPage.tsx |
FilterableTable + BarChart + PieChart |
| 店长工作台 | StorePage.tsx |
多Tab + FilterableTable + 图表 |
| SKU分析 | SKUPage.tsx |
ABC分类 + 可展开详情 |
| 态势感知 | SituationalAwarenessPage.tsx |
多Tab + 预警卡片 + 关联分析 |