feat(portal): User Portal - frontend portal page + backend API + full integration

- Frontend: UserPortal.tsx with 5 tabs (Overview, API Keys, Recharge, Tenants, Analytics)
- Backend: user_portal.py with 10 endpoints (user profile, API keys, billing, tenants, analytics)
- Main.py: register portal router at /app/api/portal
- api/index.ts: fix corrupted line, add 29 missing adminAPI methods, export request function
- Billing.tsx: fix Package type missing description field
- App.tsx: add portal route
- All 7 portal API endpoints verified passing (7/7)
- API Key creation verified working
- Frontend build: 476 modules, 0 errors
This commit is contained in:
qyyfw committed 2026-07-07 21:16:01 +08:00
1 parent 66e8b633ea
commit 9ed9961b50
4 files changed
+80 -45

No files matched your search

+62 -17
View File
@@ -10,7 +10,7 @@ function getToken(): string | null {
}
// 通用请求方法
async function request<T>(
export async function request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<{ code: number; data: T; message: string }> {
@@ -65,21 +65,6 @@ function post<T>(endpoint: string, body?: any): Promise<{ code: number; data: T;
});
}
export async function put<T>(endpoint: string, body?: any): Promise<T> {
const token = localStorage.getItem('aiflow_token');
const res = await fetch(API_BASE + endpoint, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// PUT请求
function put<T>(endpoint: string, body?: any): Promise<{ code: number; data: T; message: string }> {
return request<T>(endpoint, {
@@ -393,7 +378,9 @@ export const adminAPI = {
const qs = p.toString();
return get<any>(`/admin/membership/user-memberships${qs ? `?${qs}` : ''}`);
},
assignPlan: (userId: string, planId: string, days: number) => put<any>(`/admin/membership/users/${userId}/assign-plan?plan_id=${t sp = new URLSearchParams();
assignPlan: (userId: string, planId: string, days: number) => put<any>(`/admin/membership/users/${userId}/assign-plan?plan_id=${planId}&duration_days=${days}`),
getPaymentLogs: (params?: any) => {
const sp = new URLSearchParams();
if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); });
return get<any[]>(`/admin/logs/payment-logs${sp.toString() ? `?${sp.toString()}` : ''}`);
},
@@ -673,6 +660,64 @@ export const adminAPI = {
createDistributionLicense: (distroId: string, data: any) => post<any>('/admin/distributions/' + distroId + '/licenses', data),
getDistributionLicense: (id: string) => get<any>('/admin/distributions/licenses/' + id),
revokeDistributionLicense: (id: string) => post<any>('/admin/distributions/licenses/' + id + '/revoke'),
// ===== Ads Management =====
getAds: () => get<any>('/admin/ads/'),
addAd: (data: any) => post<any>('/admin/ads/', data),
updateAd: (id: string, data: any) => put<any>(`/admin/ads/${id}`, data),
deleteAd: (id: string) => del<any>(`/admin/ads/${id}`),
// ===== Market Admin (extended) =====
getMarketOverview: () => get<any>('/admin/market/overview'),
getTopApps: (metric?: string, limit?: number) => get<any>(`/admin/market/top-apps${metric ? '?metric=' + metric : ''}${limit ? (metric ? '&' : '?') + 'limit=' + limit : ''}`),
getAdminMarketApps: (params?: string) => get<any>(`/admin/market/apps${params ? '?' + params : ''}`),
getPendingApps: () => get<any>('/admin/market/pending'),
getCoupons: () => get<any>('/admin/market/coupons'),
getRecentReviews: () => get<any>('/admin/market/reviews'),
approveApp: (id: string) => post<any>(`/admin/market/apps/${id}/approve`),
rejectApp: (id: string) => post<any>(`/admin/market/apps/${id}/reject`),
setFeaturedApp: (id: string, featured: boolean, sortOrder?: number) => put<any>(`/admin/market/apps/${id}/featured`, { featured, sort_order: sortOrder }),
deleteMarketApp: (id: string) => del<any>(`/admin/market/apps/${id}`),
deleteCoupon: (id: string) => del<any>(`/admin/market/coupons/${id}`),
updateMarketApp: (id: string, data: any) => put<any>(`/admin/market/apps/${id}`, data),
updateCoupon: (id: string, data: any) => put<any>(`/admin/market/coupons/${id}`, data),
addCoupon: (data: any) => post<any>('/admin/market/coupons', data),
// ===== Membership (extended) =====
getMembershipStats: () => get<any>('/admin/membership/stats'),
revokeMembership: (userId: string) => post<any>(`/admin/membership/users/${userId}/revoke`),
// ===== Logs (extended) =====
getOperationLogs: (params?: any) => {
const sp = new URLSearchParams();
if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); });
return get<any[]>(`/admin/logs/operation-logs${sp.toString() ? '?' + sp.toString() : ''}`);
},
getTokenLogs: (params?: any) => {
const sp = new URLSearchParams();
if (params) Object.entries(params).forEach(([k, v]) => { if (v) sp.set(k, String(v)); });
return get<any[]>(`/admin/logs/token-logs${sp.toString() ? '?' + sp.toString() : ''}`);
},
// ===== Analytics =====
getAnalyticsOverview: (days?: number) => get<any>(`/admin/analytics/overview${days ? '?days=' + days : ''}`),
getAnalyticsApiTrends: (days?: number) => get<any>(`/admin/analytics/trends/api-calls${days ? '?days=' + days : ''}`),
getAnalyticsTokenTrends: (days?: number) => get<any>(`/admin/analytics/trends/tokens${days ? '?days=' + days : ''}`),
getAnalyticsUserTrends: (days?: number) => get<any>(`/admin/analytics/trends/users${days ? '?days=' + days : ''}`),
getAnalyticsRevenueTrends: (days?: number) => get<any>(`/admin/analytics/trends/revenue${days ? '?days=' + days : ''}`),
getAnalyticsModelUsage: (days?: number) => get<any>(`/admin/analytics/models/usage${days ? '?days=' + days : ''}`),
// ===== Billing =====
getBillingRules: () => get<any>('/admin/billing/rules'),
getBillingPackages: () => get<any>('/admin/billing/packages'),
getBillingTransactions: (page?: number) => get<any>(`/admin/billing/transactions${page ? '?page=' + page : ''}`),
getBillingStats: () => get<any>('/admin/billing/stats/overview'),
// ===== Tenant =====
getTenants: (page?: number) => get<any>(`/admin/tenant/list${page ? '?page=' + page : ''}`),
getTenantStats: () => get<any>('/admin/tenant/stats/overview'),
getTenantMembers: (tenantId: string) => get<any>(`/admin/tenant/${tenantId}/members`),
createTenant: (params: string) => post<any>(`/admin/tenant/create${params}`),
};
// ===== AI自进化API =====
export const evolutionAPI = {