feat: Wire all view components to Next.js App Router routes

- Create (admin) route group with AdminLayout wrapper
- Add page.tsx route files for all 25 view pages (dashboard, issuers,
  users, trading, risk, compliance, system, disputes, coupons, finance,
  chain, reports, merchant, agent, insurance, analytics sub-pages,
  compliance sub-pages)
- Update AdminLayout to use Next.js usePathname/useRouter for real
  URL-based navigation instead of internal state
- Add 'use client' directive to view components using useState hooks
- Fix 404 on /dashboard by creating proper App Router route structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-11 05:37:28 -08:00
parent 9ce42ed5ac
commit d49200e876
32 changed files with 6182 additions and 6 deletions

5996
frontend/admin-web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
import { AgentPanelPage } from '@/views/agent/AgentPanelPage';
export default function Agent() {
return <AgentPanelPage />;
}

View File

@ -0,0 +1,5 @@
import { ConsumerProtectionPage } from '@/views/analytics/ConsumerProtectionPage';
export default function ConsumerProtection() {
return <ConsumerProtectionPage />;
}

View File

@ -0,0 +1,5 @@
import { CouponAnalyticsPage } from '@/views/analytics/CouponAnalyticsPage';
export default function CouponAnalytics() {
return <CouponAnalyticsPage />;
}

View File

@ -0,0 +1,5 @@
import { MarketMakerPage } from '@/views/analytics/MarketMakerPage';
export default function MarketMaker() {
return <MarketMakerPage />;
}

View File

@ -0,0 +1,5 @@
import { UserAnalyticsPage } from '@/views/analytics/UserAnalyticsPage';
export default function UserAnalytics() {
return <UserAnalyticsPage />;
}

View File

@ -0,0 +1,5 @@
import { ChainMonitorPage } from '@/views/chain/ChainMonitorPage';
export default function Chain() {
return <ChainMonitorPage />;
}

View File

@ -0,0 +1,5 @@
import { IpoReadinessPage } from '@/views/compliance/IpoReadinessPage';
export default function IpoReadiness() {
return <IpoReadinessPage />;
}

View File

@ -0,0 +1,5 @@
import { LicenseManagementPage } from '@/views/compliance/LicenseManagementPage';
export default function License() {
return <LicenseManagementPage />;
}

View File

@ -0,0 +1,5 @@
import { CompliancePage } from '@/views/compliance/CompliancePage';
export default function Compliance() {
return <CompliancePage />;
}

View File

@ -0,0 +1,5 @@
import { SecFilingPage } from '@/views/compliance/SecFilingPage';
export default function SecFiling() {
return <SecFilingPage />;
}

View File

@ -0,0 +1,5 @@
import { SoxCompliancePage } from '@/views/compliance/SoxCompliancePage';
export default function Sox() {
return <SoxCompliancePage />;
}

View File

@ -0,0 +1,5 @@
import { TaxCompliancePage } from '@/views/compliance/TaxCompliancePage';
export default function Tax() {
return <TaxCompliancePage />;
}

View File

@ -0,0 +1,5 @@
import { CouponManagementPage } from '@/views/coupons/CouponManagementPage';
export default function Coupons() {
return <CouponManagementPage />;
}

View File

@ -0,0 +1,5 @@
import { DashboardPage } from '@/views/dashboard/DashboardPage';
export default function Dashboard() {
return <DashboardPage />;
}

View File

@ -0,0 +1,5 @@
import { DisputePage } from '@/views/disputes/DisputePage';
export default function Disputes() {
return <DisputePage />;
}

View File

@ -0,0 +1,5 @@
import { FinanceManagementPage } from '@/views/finance/FinanceManagementPage';
export default function Finance() {
return <FinanceManagementPage />;
}

View File

@ -0,0 +1,5 @@
import { InsurancePage } from '@/views/insurance/InsurancePage';
export default function Insurance() {
return <InsurancePage />;
}

View File

@ -0,0 +1,5 @@
import { IssuerManagementPage } from '@/views/issuers/IssuerManagementPage';
export default function Issuers() {
return <IssuerManagementPage />;
}

View File

@ -0,0 +1,9 @@
import { AdminLayout } from '@/layouts/AdminLayout';
export default function AdminGroupLayout({
children,
}: {
children: React.ReactNode;
}) {
return <AdminLayout>{children}</AdminLayout>;
}

View File

@ -0,0 +1,5 @@
import { MerchantRedemptionPage } from '@/views/merchant/MerchantRedemptionPage';
export default function Merchant() {
return <MerchantRedemptionPage />;
}

View File

@ -0,0 +1,5 @@
import { ReportsPage } from '@/views/reports/ReportsPage';
export default function Reports() {
return <ReportsPage />;
}

View File

@ -0,0 +1,5 @@
import { RiskCenterPage } from '@/views/risk/RiskCenterPage';
export default function Risk() {
return <RiskCenterPage />;
}

View File

@ -0,0 +1,5 @@
import { SystemManagementPage } from '@/views/system/SystemManagementPage';
export default function System() {
return <SystemManagementPage />;
}

View File

@ -0,0 +1,5 @@
import { TradingMonitorPage } from '@/views/trading/TradingMonitorPage';
export default function Trading() {
return <TradingMonitorPage />;
}

View File

@ -0,0 +1,5 @@
import { UserManagementPage } from '@/views/users/UserManagementPage';
export default function Users() {
return <UserManagementPage />;
}

View File

@ -1,4 +1,8 @@
'use client';
import React, { useState } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import Link from 'next/link';
/**
* D. Web管理前端 -
@ -68,12 +72,43 @@ const navItems: NavItem[] = [
],
},
{ key: 'disputes', icon: '⚖️', label: '争议处理', badge: 8 },
{ key: 'coupons', icon: '🎫', label: '券管理' },
{ key: 'finance', icon: '💰', label: '财务管理' },
{ key: 'chain', icon: '⛓️', label: '链上监控' },
{ key: 'reports', icon: '📈', label: '报表中心' },
{ key: 'merchant', icon: '🏪', label: '商户核销' },
{ key: 'agent', icon: '🤖', label: '代理面板' },
{ key: 'insurance', icon: '🛡️', label: '保险管理' },
{
key: 'analytics', icon: '📊', label: '数据分析',
children: [
{ key: 'analytics/users', icon: '', label: '用户分析' },
{ key: 'analytics/coupons', icon: '', label: '券分析' },
{ key: 'analytics/market-maker', icon: '', label: '做市商分析' },
{ key: 'analytics/consumer-protection', icon: '', label: '消费者保护' },
],
},
];
export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const pathname = usePathname();
const router = useRouter();
const [collapsed, setCollapsed] = useState(false);
const [activeKey, setActiveKey] = useState('dashboard');
const [expandedKeys, setExpandedKeys] = useState<string[]>(['issuers', 'risk']);
// Derive activeKey from current pathname
const activeKey = pathname.replace(/^\//, '') || 'dashboard';
// Auto-expand parent nav items based on current path
const getInitialExpanded = () => {
const expanded: string[] = [];
navItems.forEach(item => {
if (item.children && item.children.some(c => activeKey.startsWith(c.key) || activeKey === item.key)) {
expanded.push(item.key);
}
});
return expanded;
};
const [expandedKeys, setExpandedKeys] = useState<string[]>(getInitialExpanded);
const toggleExpand = (key: string) => {
setExpandedKeys(prev =>
@ -132,7 +167,13 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
{navItems.map(item => (
<div key={item.key}>
<button
onClick={() => item.children ? toggleExpand(item.key) : setActiveKey(item.key)}
onClick={() => {
if (item.children) {
toggleExpand(item.key);
} else {
router.push('/' + item.key);
}
}}
style={{
width: '100%',
display: 'flex',
@ -140,8 +181,8 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
padding: '10px 12px',
border: 'none',
borderRadius: 'var(--radius-sm)',
background: activeKey === item.key ? 'var(--color-primary-surface)' : 'transparent',
color: activeKey === item.key ? 'var(--color-primary)' : 'var(--color-text-secondary)',
background: activeKey === item.key || activeKey.startsWith(item.key + '/') ? 'var(--color-primary-surface)' : 'transparent',
color: activeKey === item.key || activeKey.startsWith(item.key + '/') ? 'var(--color-primary)' : 'var(--color-text-secondary)',
cursor: 'pointer',
font: 'var(--text-label)',
marginBottom: 2,
@ -178,7 +219,7 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
{item.children.map(sub => (
<button
key={sub.key}
onClick={() => setActiveKey(sub.key)}
onClick={() => router.push('/' + sub.key)}
style={{
width: '100%',
display: 'flex',

View File

@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
/**

View File

@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
/**

View File

@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
/**

View File

@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
/**

View File

@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
/**