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 CacheAdmin = lazy(() => import('./pages/CacheAdmin'))
|
||||
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 ReportingAdmin = lazy(() => import('./pages/ReportingAdmin'))
|
||||
|
||||
@ -182,6 +184,8 @@ function App() {
|
||||
/>
|
||||
<Route path="reviews" element={<Suspense fallback={<PageLoading />}><ReviewAdmin /></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="reporting" element={<Suspense fallback={<PageLoading />}><ReportingAdmin /></Suspense>} />
|
||||
<Route path="release" element={<Suspense fallback={<PageLoading />}><ReleaseAdmin /></Suspense>} />
|
||||
|
||||
@ -47,6 +47,7 @@ export const adminMenuItems: AdminMenuItem[] = [
|
||||
{ path: '/learning/anomalies', name: '异常数据' },
|
||||
{ path: '/learning/user-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 UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
|
||||
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
||||
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
|
||||
|
||||
export interface RouteConfig {
|
||||
path: string
|
||||
@ -47,4 +48,5 @@ export const routeConfig: RouteConfig[] = [
|
||||
{ path: '/learning/anomalies', title: '异常数据', element: AnomalyPage },
|
||||
{ path: '/learning/user-diagnose', title: '用户诊断', element: UserDiagnosePage },
|
||||
{ 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