feat: Admin 管理后台学习功能 - 路由、菜单、类型定义
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 12s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 12s
- App.tsx: 主应用更新 - menu.tsx: 菜单配置新增学习模块 - routes/index.tsx: 路由注册 - types/learning.ts: 学习相关类型定义 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6ad24aabe4
commit
e69930f7f4
@ -44,6 +44,8 @@ const ReviewAdmin = lazy(() => import('./pages/ReviewAdmin'))
|
|||||||
const NotificationAdmin = lazy(() => import('./pages/NotificationAdmin'))
|
const NotificationAdmin = lazy(() => import('./pages/NotificationAdmin'))
|
||||||
const CacheAdmin = lazy(() => import('./pages/CacheAdmin'))
|
const CacheAdmin = lazy(() => import('./pages/CacheAdmin'))
|
||||||
const LearningData = lazy(() => import('./pages/LearningData'))
|
const LearningData = lazy(() => import('./pages/LearningData'))
|
||||||
|
const LearningDashboard = lazy(() => import('./pages/learning/Dashboard'))
|
||||||
|
const LearningReplay = lazy(() => import('./pages/learning/ReplayPage'))
|
||||||
const BackupAdmin = lazy(() => import('./pages/BackupAdmin'))
|
const BackupAdmin = lazy(() => import('./pages/BackupAdmin'))
|
||||||
const ReportingAdmin = lazy(() => import('./pages/ReportingAdmin'))
|
const ReportingAdmin = lazy(() => import('./pages/ReportingAdmin'))
|
||||||
|
|
||||||
@ -182,6 +184,8 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
<Route path="reviews" element={<Suspense fallback={<PageLoading />}><ReviewAdmin /></Suspense>} />
|
<Route path="reviews" element={<Suspense fallback={<PageLoading />}><ReviewAdmin /></Suspense>} />
|
||||||
<Route path="learning-data" element={<Suspense fallback={<PageLoading />}><LearningData /></Suspense>} />
|
<Route path="learning-data" element={<Suspense fallback={<PageLoading />}><LearningData /></Suspense>} />
|
||||||
|
<Route path="learning" element={<Suspense fallback={<PageLoading />}><LearningDashboard /></Suspense>} />
|
||||||
|
<Route path="learning/replay" element={<Suspense fallback={<PageLoading />}><LearningReplay /></Suspense>} />
|
||||||
<Route path="backup" element={<Suspense fallback={<PageLoading />}><BackupAdmin /></Suspense>} />
|
<Route path="backup" element={<Suspense fallback={<PageLoading />}><BackupAdmin /></Suspense>} />
|
||||||
<Route path="reporting" element={<Suspense fallback={<PageLoading />}><ReportingAdmin /></Suspense>} />
|
<Route path="reporting" element={<Suspense fallback={<PageLoading />}><ReportingAdmin /></Suspense>} />
|
||||||
<Route path="release" element={<Suspense fallback={<PageLoading />}><ReleaseAdmin /></Suspense>} />
|
<Route path="release" element={<Suspense fallback={<PageLoading />}><ReleaseAdmin /></Suspense>} />
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export const adminMenuItems: AdminMenuItem[] = [
|
|||||||
{ path: '/learning/anomalies', name: '异常数据' },
|
{ path: '/learning/anomalies', name: '异常数据' },
|
||||||
{ path: '/learning/user-diagnose', name: '用户诊断' },
|
{ path: '/learning/user-diagnose', name: '用户诊断' },
|
||||||
{ path: '/learning/material-diagnose', name: '资料诊断' },
|
{ path: '/learning/material-diagnose', name: '资料诊断' },
|
||||||
|
{ path: '/learning/replay', name: '事件重放' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const AnomalyPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({
|
|||||||
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage })))
|
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage })))
|
||||||
const UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
|
const UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
|
||||||
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
||||||
|
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
|
||||||
|
|
||||||
export interface RouteConfig {
|
export interface RouteConfig {
|
||||||
path: string
|
path: string
|
||||||
@ -47,4 +48,5 @@ export const routeConfig: RouteConfig[] = [
|
|||||||
{ path: '/learning/anomalies', title: '异常数据', element: AnomalyPage },
|
{ path: '/learning/anomalies', title: '异常数据', element: AnomalyPage },
|
||||||
{ path: '/learning/user-diagnose', title: '用户诊断', element: UserDiagnosePage },
|
{ path: '/learning/user-diagnose', title: '用户诊断', element: UserDiagnosePage },
|
||||||
{ path: '/learning/material-diagnose', title: '资料诊断', element: MaterialDiagnosePage },
|
{ path: '/learning/material-diagnose', title: '资料诊断', element: MaterialDiagnosePage },
|
||||||
|
{ path: '/learning/replay', title: '事件重放', element: LearningReplay },
|
||||||
]
|
]
|
||||||
|
|||||||
146
src/types/learning.ts
Normal file
146
src/types/learning.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// ── Reading Event ──
|
||||||
|
export interface ReadingEvent {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
eventId: string;
|
||||||
|
clientSessionId: string;
|
||||||
|
readingTargetType: string;
|
||||||
|
materialId: string;
|
||||||
|
knowledgeBaseId?: string;
|
||||||
|
eventType: string;
|
||||||
|
position?: unknown;
|
||||||
|
activeSecondsDelta: number;
|
||||||
|
clientTimestampMs: number;
|
||||||
|
clientTimezoneOffsetMinutes?: number;
|
||||||
|
sequence: number;
|
||||||
|
platform?: string;
|
||||||
|
appVersion?: string;
|
||||||
|
status: string;
|
||||||
|
errorCode?: string;
|
||||||
|
warningCodes?: unknown;
|
||||||
|
serverReceivedAt: string;
|
||||||
|
processedAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Learning Session ──
|
||||||
|
export interface LearningSession {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
knowledgeBaseId?: string;
|
||||||
|
knowledgeItemId?: string;
|
||||||
|
mode: string;
|
||||||
|
status: string;
|
||||||
|
startedAt: string;
|
||||||
|
endedAt?: string;
|
||||||
|
durationSeconds: number;
|
||||||
|
focusMinutes?: number;
|
||||||
|
totalActiveSeconds: number;
|
||||||
|
clientSessionId?: string;
|
||||||
|
materialId?: string;
|
||||||
|
readingTargetType?: string;
|
||||||
|
lastPosition?: unknown;
|
||||||
|
lastEventAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Material Reading Progress ──
|
||||||
|
export interface MaterialReadingProgress {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
readingTargetType: string;
|
||||||
|
materialId: string;
|
||||||
|
knowledgeBaseId?: string;
|
||||||
|
lastClientSessionId?: string;
|
||||||
|
lastPosition?: unknown;
|
||||||
|
lastProgress?: number;
|
||||||
|
totalActiveSeconds: number;
|
||||||
|
sessionCount: number;
|
||||||
|
status: string;
|
||||||
|
firstOpenedAt?: string;
|
||||||
|
lastOpenedAt?: string;
|
||||||
|
lastReadAt?: string;
|
||||||
|
isMarkedRead: boolean;
|
||||||
|
markedReadAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Daily Learning Activity ──
|
||||||
|
export interface DailyLearningActivity {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
activityDate: string;
|
||||||
|
durationSeconds: number;
|
||||||
|
sessionsCount: number;
|
||||||
|
activeRecallCount: number;
|
||||||
|
reviewCount: number;
|
||||||
|
aiAnalysisCount: number;
|
||||||
|
completedLoopCount: number;
|
||||||
|
activityLevel: number;
|
||||||
|
readingSeconds: number;
|
||||||
|
materialsReadCount: number;
|
||||||
|
markedReadCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Learning Record ──
|
||||||
|
export interface LearningRecord {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
sessionId?: string;
|
||||||
|
recordType: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
durationSeconds: number;
|
||||||
|
occurredAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dashboard ──
|
||||||
|
export interface AdminDashboard {
|
||||||
|
overview: { totalEvents: number; todayEvents: number; failedEvents: number; duplicateEvents: number };
|
||||||
|
sessions: { active: number; interrupted: number; completed: number; total: number };
|
||||||
|
users: { activeToday: number; totalWithEvents: number };
|
||||||
|
materials: { totalRead: number; totalMarkedRead: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Anomalies ──
|
||||||
|
export interface AnomalyData {
|
||||||
|
failed: { items: ReadingEvent[]; total: number };
|
||||||
|
duplicates: { items: ReadingEvent[]; total: number };
|
||||||
|
outliers: { items: ReadingEvent[]; note: string };
|
||||||
|
stuckSessions: LearningSession[];
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Timeline ──
|
||||||
|
export interface UserTimeline {
|
||||||
|
events: { eventId: string; eventType: string; materialId: string; clientTimestampMs: number; status: string }[];
|
||||||
|
sessions: { id: string; mode: string; status: string; startedAt: string; endedAt?: string; totalActiveSeconds: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Diagnose ──
|
||||||
|
export interface UserDiagnose {
|
||||||
|
userId: string;
|
||||||
|
totalEvents: number;
|
||||||
|
recentSessions: { id: string; status: string; startedAt: string; totalActiveSeconds: number }[];
|
||||||
|
progress: { materialId: string; status: string; totalActiveSeconds: number; isMarkedRead: boolean }[];
|
||||||
|
dailyActivity: DailyLearningActivity[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MaterialDiagnose {
|
||||||
|
materialId: string;
|
||||||
|
events: ReadingEvent[];
|
||||||
|
sessions: LearningSession[];
|
||||||
|
progress: MaterialReadingProgress | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Export ──
|
||||||
|
export interface ExportResult {
|
||||||
|
exported: number;
|
||||||
|
total: number;
|
||||||
|
truncated: boolean;
|
||||||
|
format: string;
|
||||||
|
data: ReadingEvent[];
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user