fix:提交修复6个问题

This commit is contained in:
ljy
2026-07-08 23:01:18 +08:00
parent 99be8e0237
commit ab9654564e
43 changed files with 3671 additions and 277 deletions
+196 -23
View File
@@ -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>
);