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:
1 parent
66e8b633ea
commit
9ed9961b50
4 files changed
+80
-45
No files matched your search
+62
-17
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user