Implement the mobile dashboard frontend, admin overview APIs, report archive export, and local dev proxy so the boss dashboard can run against real backend data while preserving MSW demos. Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { Button, DotLoading, ErrorBlock } from 'antd-mobile'
|
||
import { KpiCard } from '../../../components/kpi/KpiCard'
|
||
import { MiniTrendChart } from '../../../components/charts/MiniTrendChart'
|
||
import { formatMoney } from '../../../utils/format'
|
||
import { useDashboardOverview } from '../api'
|
||
import { RankList } from '../components/RankList'
|
||
import { RiskAlertSection } from '../components/RiskAlertSection'
|
||
import { TodaySnapshotSection } from '../components/TodaySnapshotSection'
|
||
|
||
export function BossDashboardPage() {
|
||
const { data, isLoading, isError, refetch } = useDashboardOverview()
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<section className="loading-page">
|
||
<DotLoading color="primary" />
|
||
<p>正在生成经营简报...</p>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
if (isError || !data) {
|
||
return (
|
||
<section className="error-page">
|
||
<ErrorBlock status="default" title="驾驶舱加载失败" description="后端接口暂不可用,请确认服务、登录态或接口权限后重试。" />
|
||
<Button color="primary" onClick={() => void refetch()}>
|
||
重新加载
|
||
</Button>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
const coreKpis = data.kpis.slice(0, 4)
|
||
const moreKpis = data.kpis.slice(4)
|
||
|
||
return (
|
||
<section className="dashboard-page">
|
||
<header className="dashboard-hero">
|
||
<div className="hero-topline">
|
||
<span>经营驾驶舱</span>
|
||
<button type="button">上个工作日</button>
|
||
</div>
|
||
<p className="eyebrow">数据日期 {data.businessDate}</p>
|
||
<h1>上个工作日经营简报</h1>
|
||
<p className="hero-summary">{data.summary}</p>
|
||
<div className="hero-metric">
|
||
<span>上个工作日成交额</span>
|
||
<strong>{formatMoney(data.kpis[0]?.value)}</strong>
|
||
<small>生成时间:{data.generatedAt}</small>
|
||
</div>
|
||
</header>
|
||
|
||
<section className="kpi-grid" aria-label="核心经营指标">
|
||
{coreKpis.map((metric) => (
|
||
<KpiCard key={metric.key} metric={metric} />
|
||
))}
|
||
</section>
|
||
|
||
<section className="section-block compact-section">
|
||
<div className="section-title-row">
|
||
<div>
|
||
<p className="section-kicker">More</p>
|
||
<h2>更多经营指标</h2>
|
||
</div>
|
||
</div>
|
||
<div className="kpi-grid kpi-grid--compact">
|
||
{moreKpis.map((metric) => (
|
||
<KpiCard key={metric.key} metric={metric} />
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<TodaySnapshotSection snapshots={data.snapshots} />
|
||
|
||
<section className="section-block">
|
||
<div className="section-title-row">
|
||
<div>
|
||
<p className="section-kicker">Trend</p>
|
||
<h2>近 7 天交易趋势</h2>
|
||
</div>
|
||
</div>
|
||
<MiniTrendChart data={data.trends} />
|
||
</section>
|
||
|
||
<section className="section-block compact-section">
|
||
<div className="section-title-row">
|
||
<div>
|
||
<p className="section-kicker">Fund</p>
|
||
<h2>资金池摘要</h2>
|
||
</div>
|
||
</div>
|
||
<div className="kpi-grid kpi-grid--compact">
|
||
{data.fundPool.map((metric) => (
|
||
<KpiCard key={metric.key} metric={metric} />
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<RankList title="高价值用户" items={data.userRanks} />
|
||
<RankList title="团队贡献排行" items={data.teamRanks} />
|
||
<RankList title="高货值未成交商品" items={data.productRanks} />
|
||
<RiskAlertSection risks={data.risks} />
|
||
</section>
|
||
)
|
||
}
|