feat(partner): let sub-accounts view team leaderboard
CI / verify (pull_request) Has been cancelled

Expose same-primary ranking on home/me for all staff including warehouse roles, and open the leaderboard API without business permission gates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-17 10:02:42 +08:00
parent 972bdde650
commit 04253b9120
6 changed files with 26 additions and 14 deletions
+1 -1
View File
@@ -63,9 +63,9 @@ function SubAccountRoutes() {
<> <>
<Route path="/stores/new" element={<StoreCreatePage />} /> <Route path="/stores/new" element={<StoreCreatePage />} />
<Route path="/stores/:id" element={<StoreDetailPage />} /> <Route path="/stores/:id" element={<StoreDetailPage />} />
<Route path="/leaderboard" element={<LeaderboardPage />} />
</> </>
)} )}
<Route path="/leaderboard" element={<LeaderboardPage />} />
{isWarehouse && <Route path="/orders/:id" element={<OrderDetailPage />} />} {isWarehouse && <Route path="/orders/:id" element={<OrderDetailPage />} />}
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
+2 -2
View File
@@ -48,8 +48,8 @@ export function partnerHomePath(account: PartnerMe | null | undefined): string {
return '/'; return '/';
} }
const STORE_STAFF_PREFIXES = ['/', '/stores', '/me', '/login']; const STORE_STAFF_PREFIXES = ['/', '/stores', '/me', '/leaderboard', '/login'];
const WAREHOUSE_STAFF_PREFIXES = ['/', '/orders', '/me', '/login']; const WAREHOUSE_STAFF_PREFIXES = ['/', '/orders', '/me', '/leaderboard', '/login'];
export function isSubAccountPath(pathname: string, navKind: PartnerNavKind = 'store_staff'): boolean { export function isSubAccountPath(pathname: string, navKind: PartnerNavKind = 'store_staff'): boolean {
if (pathname === '/login') return true; if (pathname === '/login') return true;
+9
View File
@@ -251,6 +251,15 @@ export default function CenterPage({ variant = 'primary', roleLabel }: CenterPag
{!isPrimary && ( {!isPrimary && (
<section className="partner-menu-section"> <section className="partner-menu-section">
<div className="partner-menu-card"> <div className="partner-menu-card">
<Link to="/leaderboard" className="partner-menu-item">
<div className="partner-menu-item-left">
<div className="partner-menu-icon">
<span className="material-symbols-outlined">emoji_events</span>
</div>
<span className="body-md" style={{ fontSize: 16 }}></span>
</div>
<span className="material-symbols-outlined text-muted">chevron_right</span>
</Link>
<button type="button" className="partner-menu-item" onClick={() => { if (!contactSupport()) toastError('暂无客服电话'); }}> <button type="button" className="partner-menu-item" onClick={() => { if (!contactSupport()) toastError('暂无客服电话'); }}>
<div className="partner-menu-item-left"> <div className="partner-menu-item-left">
<div className="partner-menu-icon"><span className="material-symbols-outlined">support_agent</span></div> <div className="partner-menu-icon"><span className="material-symbols-outlined">support_agent</span></div>
+11 -10
View File
@@ -96,7 +96,7 @@ function LeaderboardPreview({ entries }: { entries: PartnerLeaderboardEntry[] })
<div className="partner-leaderboard-header"> <div className="partner-leaderboard-header">
<h3 className="headline-md" style={{ display: 'flex', alignItems: 'center', gap: 8 }}> <h3 className="headline-md" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="material-symbols-outlined text-primary" style={{ fontSize: 20 }}>emoji_events</span> <span className="material-symbols-outlined text-primary" style={{ fontSize: 20 }}>emoji_events</span>
</h3> </h3>
<Link to="/leaderboard" className="label-md text-primary" style={{ display: 'flex', alignItems: 'center', gap: 2 }}> <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> <span className="material-symbols-outlined" style={{ fontSize: 14 }}>chevron_right</span>
@@ -158,16 +158,17 @@ export default function HomePage() {
if (!isWarehouse) { if (!isWarehouse) {
request<Record<string, unknown>>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {}); request<Record<string, unknown>>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {});
request<Array<Record<string, unknown>>>('PARTNER_H5', '/partner/stores').then(setStores).catch(() => []); request<Array<Record<string, unknown>>>('PARTNER_H5', '/partner/stores').then(setStores).catch(() => []);
fetchPartnerLeaderboard('month')
.then((data) => {
setLeaderboardEntries(data.list.slice(0, LEADERBOARD_PREVIEW_LIMIT));
})
.catch(() => {
setLeaderboardEntries([]);
});
} else { } else {
request<Record<string, unknown>>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {}); request<Record<string, unknown>>('PARTNER_H5', '/partner/dashboard').then(setDash).catch(() => {});
} }
// 主账号与全部子账号均可查看同团队贡献榜
fetchPartnerLeaderboard('month')
.then((data) => {
setLeaderboardEntries(data.list.slice(0, LEADERBOARD_PREVIEW_LIMIT));
})
.catch(() => {
setLeaderboardEntries([]);
});
}, [navigate, isWarehouse, isPrimary, warehouseOk]); }, [navigate, isWarehouse, isPrimary, warehouseOk]);
const storeCount = Number(dash?.storeCount || stores.length || 0); const storeCount = Number(dash?.storeCount || stores.length || 0);
@@ -317,10 +318,10 @@ export default function HomePage() {
</div> </div>
</div> </div>
</section> </section>
<LeaderboardPreview entries={leaderboardEntries} />
</> </>
)} )}
<LeaderboardPreview entries={leaderboardEntries} />
</main> </main>
</div> </div>
); );
@@ -47,7 +47,7 @@ export default function LeaderboardPage() {
return ( return (
<div className="page-no-tab"> <div className="page-no-tab">
<PageHeader title="合伙人贡献榜" onBack={() => navigate('/')} /> <PageHeader title="团队贡献榜" onBack={() => navigate('/')} />
<div className="partner-leaderboard-tabs"> <div className="partner-leaderboard-tabs">
{PERIOD_TABS.map((tab) => ( {PERIOD_TABS.map((tab) => (
@@ -89,6 +89,8 @@ export class PartnerDashboardController {
} }
@Get('leaderboard') @Get('leaderboard')
/** 任意合伙人子账号可看同主账号排行(激励),不校验业务权限点 */
@RequirePartnerPermissions()
leaderboard( leaderboard(
@CurrentUser() user: AuthUser, @CurrentUser() user: AuthUser,
@Query('period') period?: string, @Query('period') period?: string,