diff --git a/src/pages/learning/DataPages.tsx b/src/pages/learning/DataPages.tsx
index 53b2e25..c175cf2 100644
--- a/src/pages/learning/DataPages.tsx
+++ b/src/pages/learning/DataPages.tsx
@@ -1,5 +1,7 @@
import { useState } from 'react';
-import { Table, Input, Select, Space, Tag, Button, Tabs, Drawer, Descriptions, DatePicker } from 'antd';
+import { Table, Input, Select, Space, Tag, Button, Tabs, Drawer, Descriptions, DatePicker, Timeline, Typography } from 'antd';
+
+const { Text } = Typography;
import { useQuery } from '@tanstack/react-query';
import { learningAdminAPI } from '../../services/learningAdmin';
import dayjs from 'dayjs';
@@ -410,6 +412,47 @@ export function AnomalyPage() {
);
}
+// ── User Timeline ──
+
+export function UserTimelinePage() {
+ const [userId, setUserId] = useState('');
+ const { data, isFetching, refetch } = useQuery({
+ queryKey: ['admin-user-timeline', userId],
+ queryFn: () => learningAdminAPI.getUserTimeline(userId),
+ enabled: false,
+ });
+
+ const items = Array.isArray(data)
+ ? (data as any[]).map((entry: any, i: number) => ({
+ key: i,
+ color: entry.recordType === 'session' ? 'blue' : entry.recordType === 'reading' ? 'green' : 'gray',
+ children: (
+
+
{entry.occurredAt ? dayjs(entry.occurredAt).format('YYYY-MM-DD HH:mm:ss') : '-'}
+
{entry.recordType}
+
{entry.title || '-'}
+ {entry.durationSeconds != null &&
时长 {entry.durationSeconds}s
}
+ {entry.materialId &&
资料 {entry.materialId}
}
+
+ ),
+ }))
+ : [];
+
+ return (
+
+
+ setUserId(e.target.value)} style={{ width: 300 }} />
+
+
+ {data === undefined && !isFetching && 输入用户 ID 后点击查询}
+ {isFetching && 加载中...}
+ {data && !Array.isArray(data) ? {JSON.stringify(data as any, null, 2)} : null}
+ {Array.isArray(data) && data.length === 0 && !isFetching && 该用户暂无学习记录}
+ {items.length > 0 && }
+
+ );
+}
+
// ── User Diagnose ──
export function UserDiagnosePage() {
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index cf35bf7..712b5b0 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -13,6 +13,7 @@ const ProgressPage = lazy(() => import('@/pages/learning/DataPages').then(m => (
const DailyActivityPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.DailyActivityPage })))
const RecordPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.RecordPage })))
const AnomalyPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.AnomalyPage })))
+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 })))
@@ -44,6 +45,7 @@ export const routeConfig: RouteConfig[] = [
{ path: '/learning/progress', title: '阅读进度', element: ProgressPage },
{ path: '/learning/daily', title: '每日活动', element: DailyActivityPage },
{ path: '/learning/records', title: '学习记录', element: RecordPage },
+ { path: '/learning/timeline', title: '用户时间线', element: UserTimelinePage },
{ path: '/learning/anomalies', title: '异常数据', element: AnomalyPage },
{ path: '/learning/user-diagnose', title: '用户诊断', element: UserDiagnosePage },
{ path: '/learning/material-diagnose', title: '资料诊断', element: MaterialDiagnosePage },