fix:提交修复6个问题
This commit is contained in:
@@ -1,42 +1,215 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { PartnerWeeklyReportResponse } from '@dukang/shared-types';
|
||||
import PageHeader from '@dukang/shared-ui/PageHeader';
|
||||
import { fetchPartnerWeeklyReport } from '../lib/weeklyReport';
|
||||
|
||||
function fmtMoney(n: number) {
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
|
||||
}
|
||||
|
||||
function fmtGrowth(n: number) {
|
||||
const sign = n > 0 ? '+' : '';
|
||||
return `${sign}${n}%`;
|
||||
}
|
||||
|
||||
/** BUWR-25:经营周报(preV1 占位,待 `/partner/reports/weekly` 接入) */
|
||||
export default function WeeklyReportPage() {
|
||||
const navigate = useNavigate();
|
||||
const [selectedStart, setSelectedStart] = useState<string | undefined>();
|
||||
const [data, setData] = useState<PartnerWeeklyReportResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
fetchPartnerWeeklyReport(selectedStart)
|
||||
.then(setData)
|
||||
.catch((e) => {
|
||||
setData(null);
|
||||
setError(e instanceof Error ? e.message : '加载失败');
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, [selectedStart]);
|
||||
|
||||
const maxDailyGmv = useMemo(() => {
|
||||
if (!data?.dailyGmv.length) return 1;
|
||||
return Math.max(1, ...data.dailyGmv.map((item) => item.amount));
|
||||
}, [data]);
|
||||
|
||||
const topStores = data?.storeRanking.slice(0, 3) ?? [];
|
||||
const summary = data?.summary;
|
||||
const growthPositive = (summary?.gmvGrowthPercent ?? 0) >= 0;
|
||||
|
||||
return (
|
||||
<div className="page-no-tab">
|
||||
<PageHeader title="数据周报" onBack={() => navigate('/')} />
|
||||
|
||||
<main style={{ padding: '0 20px 24px' }}>
|
||||
<section className="partner-revenue-card" style={{ marginBottom: 16 }}>
|
||||
<p className="partner-revenue-label">近 7 日 GMV (CNY)</p>
|
||||
<div className="partner-revenue-amount">{fmtMoney(128450)}</div>
|
||||
<main className="partner-weekly-page">
|
||||
<section className="partner-weekly-title-section">
|
||||
<h2>经营数据周报</h2>
|
||||
{data && (
|
||||
<div className="partner-weekly-tabs">
|
||||
{data.availablePeriods.map((period) => (
|
||||
<button
|
||||
key={period.startDate}
|
||||
type="button"
|
||||
className={`partner-weekly-tab${
|
||||
period.startDate === data.period.startDate ? ' active' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedStart(period.startDate)}
|
||||
>
|
||||
{period.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="partner-bento-card" style={{ marginBottom: 12 }}>
|
||||
<div className="partner-expansion-split">
|
||||
<div>
|
||||
<p className="label-md text-muted">购酒订单量</p>
|
||||
<p className="headline-lg" style={{ fontSize: 24, marginTop: 4 }}>86</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="label-md text-muted">活跃门店数</p>
|
||||
<p className="headline-lg" style={{ fontSize: 24, marginTop: 4 }}>12</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{loading && <p className="label-md text-muted partner-weekly-state">加载中…</p>}
|
||||
{!loading && error && (
|
||||
<p className="label-md text-error partner-weekly-state">{error}</p>
|
||||
)}
|
||||
|
||||
<section className="partner-bento-card">
|
||||
<h2 className="headline-md" style={{ marginBottom: 12 }}>本周新签门店</h2>
|
||||
<p className="headline-lg text-primary">3 家</p>
|
||||
<p className="label-md text-muted" style={{ marginTop: 8 }}>详细趋势图待 API 接入后展示</p>
|
||||
</section>
|
||||
{!loading && !error && data && summary && (
|
||||
<>
|
||||
<section className="partner-weekly-hero">
|
||||
<div className="partner-weekly-hero-top">
|
||||
<div>
|
||||
<p className="partner-weekly-hero-label">累计成交额 (GMV)</p>
|
||||
<p className="partner-weekly-hero-gmv">¥{fmtMoney(summary.gmv)}</p>
|
||||
</div>
|
||||
<div
|
||||
className={`partner-weekly-growth-badge${
|
||||
growthPositive ? ' partner-weekly-growth-badge--up' : ' partner-weekly-growth-badge--down'
|
||||
}`}
|
||||
>
|
||||
<span className="material-symbols-outlined">
|
||||
{growthPositive ? 'trending_up' : 'trending_down'}
|
||||
</span>
|
||||
<span>{fmtGrowth(summary.gmvGrowthPercent)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="partner-weekly-hero-grid">
|
||||
<div>
|
||||
<p className="label-md text-muted">活跃门店</p>
|
||||
<p className="partner-weekly-stat-value">
|
||||
{summary.activeStoreCount}
|
||||
<span className="partner-weekly-stat-sub">/ {summary.totalStoreCount}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="label-md text-muted">订单总量</p>
|
||||
<p className="partner-weekly-stat-value">{summary.orderCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="partner-weekly-card">
|
||||
<div className="partner-weekly-card-header">
|
||||
<div className="partner-weekly-card-title">
|
||||
<span className="material-symbols-outlined partner-weekly-icon-amber">storefront</span>
|
||||
<h3 className="headline-md">本周新签约门店</h3>
|
||||
</div>
|
||||
<p className="partner-weekly-target">
|
||||
{summary.newStoreCount}
|
||||
<span className="partner-weekly-target-sub">/ {summary.newStoreTarget}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="partner-weekly-progress-track">
|
||||
<div
|
||||
className="partner-weekly-progress-bar"
|
||||
style={{ width: `${summary.newStoreProgressPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="label-md text-muted partner-weekly-progress-hint">
|
||||
已完成本周目标的 {summary.newStoreProgressPercent}%。
|
||||
{summary.newStoreCount < summary.newStoreTarget
|
||||
? `还差 ${summary.newStoreTarget - summary.newStoreCount} 家!`
|
||||
: '目标已达成!'}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="partner-weekly-card">
|
||||
<div className="partner-weekly-card-header">
|
||||
<h3 className="headline-md">每日成交趋势</h3>
|
||||
<span className="label-md text-muted">过去 7 天</span>
|
||||
</div>
|
||||
<div className="partner-weekly-chart">
|
||||
<div className="partner-weekly-chart-bars">
|
||||
{data.dailyGmv.map((item) => {
|
||||
const height = Math.max(8, Math.round((item.amount / maxDailyGmv) * 100));
|
||||
const isMax = item.amount === maxDailyGmv && item.amount > 0;
|
||||
return (
|
||||
<div
|
||||
key={item.date}
|
||||
className={`partner-weekly-chart-bar${isMax ? ' partner-weekly-chart-bar--peak' : ''}`}
|
||||
style={{ height: `${height}%` }}
|
||||
title={`${item.weekdayLabel} ¥${fmtMoney(item.amount)}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="partner-weekly-chart-labels">
|
||||
{data.dailyGmv.map((item) => (
|
||||
<span key={item.date}>{item.weekdayLabel}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="partner-weekly-rank-section">
|
||||
<div className="partner-weekly-card-header">
|
||||
<h3 className="headline-md">门店业绩排行</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="partner-weekly-link"
|
||||
onClick={() => navigate('/stores')}
|
||||
>
|
||||
查看全部
|
||||
<span className="material-symbols-outlined">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
{topStores.length === 0 ? (
|
||||
<p className="label-md text-muted partner-weekly-empty">本周暂无核销排行数据</p>
|
||||
) : (
|
||||
<div className="partner-weekly-rank-list">
|
||||
{topStores.map((store) => (
|
||||
<div
|
||||
key={store.storeId}
|
||||
className={`partner-weekly-rank-row${
|
||||
store.rank === 1 ? ' partner-weekly-rank-row--first' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="partner-weekly-rank-badge">
|
||||
{store.rank === 1 ? (
|
||||
<span className="material-symbols-outlined">workspace_premium</span>
|
||||
) : (
|
||||
<span>{store.rank}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="partner-weekly-rank-info">
|
||||
<p className="partner-weekly-rank-name">{store.name}</p>
|
||||
{store.subtitle && (
|
||||
<p className="label-md text-muted">{store.subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
<p className="partner-weekly-rank-amount">¥{fmtMoney(store.redeemAmount)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="partner-weekly-insight">
|
||||
<span className="material-symbols-outlined">tips_and_updates</span>
|
||||
<div>
|
||||
<p className="headline-md">合伙人经营策略</p>
|
||||
<p className="body-md text-muted">{data.insight}</p>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user