feat: UserTimelinePage with Timeline component (ADMIN-INFO-009)
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 8s
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 8s
- UserTimelinePage: userId input → API query → Ant Design Timeline - Supports array timeline + fallback JSON view for unknown formats - Loading / empty / error states - Route: /learning/timeline Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a2cb0681d9
commit
0fd0709b8b
@ -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: (
|
||||
<div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{entry.occurredAt ? dayjs(entry.occurredAt).format('YYYY-MM-DD HH:mm:ss') : '-'}</Text>
|
||||
<div><Tag style={{ marginTop: 4 }}>{entry.recordType}</Tag></div>
|
||||
<Text strong>{entry.title || '-'}</Text>
|
||||
{entry.durationSeconds != null && <div><Text type="secondary">时长 {entry.durationSeconds}s</Text></div>}
|
||||
{entry.materialId && <div><Text type="secondary">资料 {entry.materialId}</Text></div>}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<PageWrapper title="用户学习时间线">
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Input placeholder="User ID" value={userId} onChange={e => setUserId(e.target.value)} style={{ width: 300 }} />
|
||||
<Button type="primary" onClick={() => refetch()} loading={isFetching}>查询</Button>
|
||||
</Space>
|
||||
{data === undefined && !isFetching && <Text type="secondary">输入用户 ID 后点击查询</Text>}
|
||||
{isFetching && <Text type="secondary">加载中...</Text>}
|
||||
{data && !Array.isArray(data) ? <pre style={{ background: '#f5f5f5', padding: 16, borderRadius: 8, overflow: 'auto', maxHeight: 600 }}>{JSON.stringify(data as any, null, 2)}</pre> : null}
|
||||
{Array.isArray(data) && data.length === 0 && !isFetching && <Text type="secondary">该用户暂无学习记录</Text>}
|
||||
{items.length > 0 && <Timeline items={items} />}
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// ── User Diagnose ──
|
||||
|
||||
export function UserDiagnosePage() {
|
||||
|
||||
@ -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 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user