@@ -5,12 +5,10 @@ import { isLoggedIn, request } from '../lib/api';
|
||||
import { fetchPartnerLeaderboard } from '../lib/leaderboard';
|
||||
import { getPartnerNavKind } from '../lib/partnerAccess';
|
||||
import { usePartnerSession } from '../contexts/PartnerSessionContext';
|
||||
import { storeStatusLabel, storeStatusPillClass } from '../lib/storeStatus';
|
||||
|
||||
function fmtMoney(n: number) {
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
const HOME_STORE_PREVIEW_LIMIT = 6;
|
||||
|
||||
const LEADERBOARD_PREVIEW_LIMIT = 3;
|
||||
|
||||
@@ -92,67 +90,42 @@ function summarizeOrders(orders: Array<Record<string, unknown>>) {
|
||||
return { pendingShip, shipping, completed, todayCount };
|
||||
}
|
||||
|
||||
function LeaderboardPreview({
|
||||
entries,
|
||||
self,
|
||||
}: {
|
||||
entries: PartnerLeaderboardEntry[];
|
||||
self?: PartnerLeaderboardEntry;
|
||||
}) {
|
||||
const selfInList = self ? entries.some((e) => e.accountId === self.accountId) : false;
|
||||
|
||||
function LeaderboardPreview({ entries }: { entries: PartnerLeaderboardEntry[] }) {
|
||||
return (
|
||||
<div className="partner-leaderboard-section">
|
||||
<section className="partner-bento-card partner-leaderboard-preview">
|
||||
<div className="partner-leaderboard-header">
|
||||
<h3 className="headline-md" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span className="material-symbols-outlined text-primary" style={{ fontSize: 18 }}>military_tech</span>
|
||||
<span className="material-symbols-outlined text-primary" style={{ fontSize: 20 }}>emoji_events</span>
|
||||
合伙人贡献榜
|
||||
</h3>
|
||||
<Link to="/leaderboard" className="label-md text-primary">查看全部</Link>
|
||||
<Link to="/leaderboard" className="label-md text-primary" style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
查看全部 <span className="material-symbols-outlined" style={{ fontSize: 14 }}>chevron_right</span>
|
||||
</Link>
|
||||
</div>
|
||||
{entries.length === 0 && !self ? (
|
||||
{entries.length === 0 ? (
|
||||
<p className="label-md text-muted">暂无排行数据</p>
|
||||
) : (
|
||||
<>
|
||||
{entries.map((entry) => (
|
||||
<div
|
||||
key={entry.accountId}
|
||||
className={`partner-leaderboard-row partner-leaderboard-row--compact${entry.isSelf ? ' partner-leaderboard-row--self' : ''}`}
|
||||
>
|
||||
<div className="partner-leaderboard-rank">{entry.rank}</div>
|
||||
<div className="partner-leaderboard-info">
|
||||
<p className="body-md" style={{ fontWeight: 600 }}>
|
||||
{entry.name}
|
||||
{entry.isSelf ? <span className="partner-leaderboard-self-tag">我的排名</span> : null}
|
||||
<span className="label-md text-muted" style={{ marginLeft: 4 }}>({entry.roleLabel})</span>
|
||||
</p>
|
||||
<p className="label-md text-muted">累计拓店 {entry.totalStores} 间</p>
|
||||
</div>
|
||||
<div className="partner-leaderboard-stat">
|
||||
<p className="headline-md text-primary">{entry.periodStores} 间</p>
|
||||
<p className="label-md text-muted">本月新增</p>
|
||||
</div>
|
||||
entries.map((entry) => (
|
||||
<div key={entry.accountId} className="partner-leaderboard-row partner-leaderboard-row--compact">
|
||||
<div className={`partner-leaderboard-avatar partner-leaderboard-avatar--rank-${Math.min(entry.rank, 3)}`}>
|
||||
<span className="material-symbols-outlined">person</span>
|
||||
<span className="partner-leaderboard-avatar-badge">{entry.rank}</span>
|
||||
</div>
|
||||
))}
|
||||
{self && !selfInList ? (
|
||||
<div className="partner-leaderboard-row partner-leaderboard-row--compact partner-leaderboard-row--self">
|
||||
<div className="partner-leaderboard-rank">{self.rank}</div>
|
||||
<div className="partner-leaderboard-info">
|
||||
<p className="body-md" style={{ fontWeight: 600 }}>
|
||||
{self.name}
|
||||
<span className="partner-leaderboard-self-tag">我的排名</span>
|
||||
</p>
|
||||
<p className="label-md text-muted">累计拓店 {self.totalStores} 间</p>
|
||||
</div>
|
||||
<div className="partner-leaderboard-stat">
|
||||
<p className="headline-md text-primary">{self.periodStores} 间</p>
|
||||
<p className="label-md text-muted">本月新增</p>
|
||||
</div>
|
||||
<div className="partner-leaderboard-info">
|
||||
<p className="body-md" style={{ fontWeight: 600 }}>
|
||||
{entry.name}
|
||||
<span className="label-md text-muted" style={{ marginLeft: 4 }}>({entry.roleLabel})</span>
|
||||
</p>
|
||||
<p className="label-md text-muted">累计拓店: {entry.totalStores} 间</p>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
<div className="partner-leaderboard-stat">
|
||||
<p className="headline-md text-primary">{entry.periodStores} 间</p>
|
||||
<p className="label-md text-muted">本月新增</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,7 +140,6 @@ export default function HomePage() {
|
||||
const [stores, setStores] = useState<Array<Record<string, unknown>>>([]);
|
||||
const [orders, setOrders] = useState<Array<Record<string, unknown>>>([]);
|
||||
const [leaderboardEntries, setLeaderboardEntries] = useState<PartnerLeaderboardEntry[]>([]);
|
||||
const [leaderboardSelf, setLeaderboardSelf] = useState<PartnerLeaderboardEntry | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
document.title = '工作台';
|
||||
@@ -186,11 +158,9 @@ export default function HomePage() {
|
||||
fetchPartnerLeaderboard('month')
|
||||
.then((data) => {
|
||||
setLeaderboardEntries(data.list.slice(0, LEADERBOARD_PREVIEW_LIMIT));
|
||||
setLeaderboardSelf(data.self);
|
||||
})
|
||||
.catch(() => {
|
||||
setLeaderboardEntries([]);
|
||||
setLeaderboardSelf(undefined);
|
||||
});
|
||||
} else {
|
||||
request<Record<string, unknown>>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {});
|
||||
@@ -207,8 +177,16 @@ export default function HomePage() {
|
||||
const now = new Date();
|
||||
return created.getMonth() === now.getMonth() && created.getFullYear() === now.getFullYear();
|
||||
}).length;
|
||||
const lastMonthNew = stores.filter((s) => {
|
||||
const created = new Date(String(s.createdAt || ''));
|
||||
const now = new Date();
|
||||
const last = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
return created.getMonth() === last.getMonth() && created.getFullYear() === last.getFullYear();
|
||||
}).length;
|
||||
const monthGrowthPct = lastMonthNew > 0
|
||||
? Math.round(((monthNew - lastMonthNew) / lastMonthNew) * 100)
|
||||
: (monthNew > 0 ? 100 : 0);
|
||||
|
||||
const previewStores = stores.slice(0, HOME_STORE_PREVIEW_LIMIT);
|
||||
const orderCount = Number(dash?.orderCount || orderStats.todayCount || 0);
|
||||
const revenue = orderCount * 128.45;
|
||||
const profit = revenue * 0.25;
|
||||
@@ -309,55 +287,31 @@ export default function HomePage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="partner-home-store-section">
|
||||
<div className="partner-home-store-header">
|
||||
<h2 className="headline-md">辖区门店</h2>
|
||||
<Link to="/stores" className="label-md text-primary">查看全部</Link>
|
||||
</div>
|
||||
{previewStores.length === 0 ? (
|
||||
<div className="partner-home-store-empty">
|
||||
<p className="label-md text-muted">暂无门店</p>
|
||||
<Link to={isPrimary ? '/stores/new' : '/stores/new?step=1'} className="label-md text-primary">录入新店</Link>
|
||||
</div>
|
||||
) : (
|
||||
previewStores.map((s) => {
|
||||
const storeId = String(s.id);
|
||||
const status = String(s.status || 'PAUSED').toUpperCase();
|
||||
const audit = String(s.auditStatus || 'APPROVED').toUpperCase();
|
||||
return (
|
||||
<Link key={storeId} to={`/stores/${storeId}`} className="partner-home-store-row">
|
||||
<div className="partner-home-store-info">
|
||||
<p className="headline-md">{String(s.name || '未命名门店')}</p>
|
||||
<p className="label-md text-muted">{String(s.address || s.district || '')}</p>
|
||||
</div>
|
||||
<span className={`partner-status-pill ${audit !== 'APPROVED' ? storeStatusPillClass(audit === 'REJECTED' ? 'CLOSED' : 'PAUSED') : storeStatusPillClass(status)}`}>
|
||||
{audit === 'PENDING' ? '待审核' : audit === 'REJECTED' ? '已驳回' : storeStatusLabel(status)}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="partner-expansion-card">
|
||||
<h2 className="headline-md" style={{ marginBottom: 16 }}>拓店情况</h2>
|
||||
<div className="partner-expansion-split">
|
||||
<div>
|
||||
<p className="label-md text-muted" style={{ marginBottom: 4 }}>本月新增签约</p>
|
||||
<span className="headline-lg" style={{ fontSize: 24 }}>{monthNew}</span>
|
||||
<div className="partner-expansion-value-row">
|
||||
<span className="headline-lg" style={{ fontSize: 24 }}>{monthNew}</span>
|
||||
{monthGrowthPct !== 0 && (
|
||||
<span className={`partner-expansion-trend${monthGrowthPct > 0 ? ' partner-expansion-trend--up' : ' partner-expansion-trend--down'}`}>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>
|
||||
{monthGrowthPct > 0 ? 'trending_up' : 'trending_down'}
|
||||
</span>
|
||||
{Math.abs(monthGrowthPct)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="label-md text-muted" style={{ marginBottom: 4 }}>待审核门店</p>
|
||||
<span className="headline-lg" style={{ fontSize: 24 }}>{pendingAuditCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LeaderboardPreview entries={leaderboardEntries} self={leaderboardSelf} />
|
||||
|
||||
<p className="label-md text-muted" style={{ paddingTop: 16, borderTop: '1px solid rgba(226,190,188,0.1)' }}>
|
||||
{String(dash?.companyName || '郑州合伙人')} · 辖区管理
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<LeaderboardPreview entries={leaderboardEntries} />
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user