diff --git a/apps/web/src/pages/health/StatisticsDashboard/DoctorDashboard.tsx b/apps/web/src/pages/health/StatisticsDashboard/DoctorDashboard.tsx index f4c41c8..aba0e20 100644 --- a/apps/web/src/pages/health/StatisticsDashboard/DoctorDashboard.tsx +++ b/apps/web/src/pages/health/StatisticsDashboard/DoctorDashboard.tsx @@ -12,6 +12,7 @@ import { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { pointsApi, type PersonalStats } from '../../../api/health/points'; import { alertApi, type Alert } from '../../../api/health/alerts'; +import { consultationApi, type Session } from '../../../api/health/consultations'; import { useStatsData } from './useStatsData'; import { useCountUp } from '../../../hooks/useCountUp'; import { SEVERITY_COLOR, SEVERITY_LABEL } from '../../../constants/health'; @@ -20,6 +21,7 @@ export function DoctorDashboard() { const navigate = useNavigate(); const [personal, setPersonal] = useState(null); const [recentAlerts, setRecentAlerts] = useState([]); + const [activeConsultations, setActiveConsultations] = useState([]); const [loading, setLoading] = useState(true); const { consultationStats } = useStatsData(); const myPatientsCount = useCountUp(personal?.my_patients ?? 0); @@ -45,7 +47,16 @@ export function DoctorDashboard() { } }, []); - useEffect(() => { fetchPersonal(); fetchRecentAlerts(); }, [fetchPersonal, fetchRecentAlerts]); + const fetchConsultations = useCallback(async () => { + try { + const result = await consultationApi.listSessions({ status: 'active', page: 1, page_size: 5 }); + setActiveConsultations(result.data); + } catch { + // 静默降级 + } + }, []); + + useEffect(() => { fetchPersonal(); fetchRecentAlerts(); fetchConsultations(); }, [fetchPersonal, fetchRecentAlerts, fetchConsultations]); if (loading && !personal) return ; @@ -197,9 +208,19 @@ export function DoctorDashboard() { } + renderItem={(session) => ( + + + {session.patient_name ?? '患者'} + {session.status === 'active' ? '进行中' : session.status} + + {session.last_message_at ? new Date(session.last_message_at).toLocaleString('zh-CN') : ''} + + + + )} /> diff --git a/apps/web/src/pages/health/StatisticsDashboard/NurseDashboard.tsx b/apps/web/src/pages/health/StatisticsDashboard/NurseDashboard.tsx index 13abf61..3ed01fe 100644 --- a/apps/web/src/pages/health/StatisticsDashboard/NurseDashboard.tsx +++ b/apps/web/src/pages/health/StatisticsDashboard/NurseDashboard.tsx @@ -7,10 +7,12 @@ import { } from '@ant-design/icons'; import { useEffect, useState, useCallback } from 'react'; import { pointsApi, type PersonalStats } from '../../../api/health/points'; +import { followUpApi, type FollowUpTask } from '../../../api/health/followUp'; import { useCountUp } from '../../../hooks/useCountUp'; export function NurseDashboard() { const [personal, setPersonal] = useState(null); + const [followUpTasks, setFollowUpTasks] = useState([]); const [loading, setLoading] = useState(true); const appointmentCount = useCountUp(personal?.today_appointments ?? 0); const overdueCount = useCountUp(personal?.overdue_follow_ups ?? 0); @@ -26,7 +28,16 @@ export function NurseDashboard() { } }, []); - useEffect(() => { fetchPersonal(); }, [fetchPersonal]); + const fetchFollowUps = useCallback(async () => { + try { + const result = await followUpApi.listTasks({ status: 'pending', page: 1, page_size: 10 }); + setFollowUpTasks(result.data); + } catch { + // 静默降级 + } + }, []); + + useEffect(() => { fetchPersonal(); fetchFollowUps(); }, [fetchPersonal, fetchFollowUps]); if (loading && !personal) return ; @@ -63,12 +74,22 @@ export function NurseDashboard() { {/* 今日随访队列 */} - + } + renderItem={(task) => ( + + + {task.patient_name ?? '患者'} + {task.follow_up_type} + + {task.planned_date ? new Date(task.planned_date).toLocaleDateString('zh-CN') : ''} + + + + )} /> diff --git a/apps/web/src/pages/health/StatisticsDashboard/OperatorDashboard.tsx b/apps/web/src/pages/health/StatisticsDashboard/OperatorDashboard.tsx index 4ca271f..1fa5991 100644 --- a/apps/web/src/pages/health/StatisticsDashboard/OperatorDashboard.tsx +++ b/apps/web/src/pages/health/StatisticsDashboard/OperatorDashboard.tsx @@ -5,15 +5,29 @@ import { CalendarOutlined, ShoppingOutlined, } from '@ant-design/icons'; +import { useEffect, useState, useCallback } from 'react'; import { useStatsData } from './useStatsData'; +import { articleApi, type ArticleListItem } from '../../../api/health/articles'; import { useCountUp } from '../../../hooks/useCountUp'; export function OperatorDashboard() { const { pointsStats, loading } = useStatsData(); + const [topArticles, setTopArticles] = useState([]); const issuedCount = useCountUp(pointsStats?.total_issued ?? 0); const spentCount = useCountUp(pointsStats?.total_spent ?? 0); const activeCount = useCountUp(pointsStats?.active_accounts ?? 0); + const fetchTopArticles = useCallback(async () => { + try { + const result = await articleApi.list({ status: 'published', page: 1, page_size: 5 }); + setTopArticles(result.data); + } catch { + // 静默降级 + } + }, []); + + useEffect(() => { fetchTopArticles(); }, [fetchTopArticles]); + if (loading && !pointsStats) return ; return ( @@ -75,9 +89,16 @@ export function OperatorDashboard() { } + renderItem={(article) => ( + + {article.title} + + {article.view_count} 次阅读 + + + )} /> diff --git a/apps/web/src/pages/health/components/workbench/OperatorWorkbench.tsx b/apps/web/src/pages/health/components/workbench/OperatorWorkbench.tsx index 29afdb3..855021b 100644 --- a/apps/web/src/pages/health/components/workbench/OperatorWorkbench.tsx +++ b/apps/web/src/pages/health/components/workbench/OperatorWorkbench.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '../../../../stores/auth'; -import { actionInboxApi, type WorkbenchStats } from '../../../../api/health/actionInbox'; +import { actionInboxApi, type WorkbenchStats, type ActionItem } from '../../../../api/health/actionInbox'; import { useStatsData } from '../../StatisticsDashboard/useStatsData'; import { dashboardApi, @@ -9,10 +9,22 @@ import { type ArticleStatsResp, } from '../../../../api/health/dashboard'; +const TYPE_ICON: Record = { + ai_suggestion: { icon: '🤖', bg: '#F0F9FF', color: '#0284C7' }, + alert: { icon: '⚠️', bg: '#FFF1F2', color: '#E11D48' }, + followup: { icon: '📋', bg: '#F0FDF4', color: '#16A34A' }, + data_anomaly: { icon: '📊', bg: '#F5F3FF', color: '#7C3AED' }, +}; + +const PRIORITY_LABEL: Record = { + urgent: '紧急', high: '高', medium: '中', low: '低', +}; + export default function OperatorWorkbench() { const navigate = useNavigate(); const user = useAuthStore((s) => s.user); const [stats, setStats] = useState(null); + const [actionItems, setActionItems] = useState([]); const [pointsActivity, setPointsActivity] = useState([]); const [articleStats, setArticleStats] = useState(null); const statsData = useStatsData(); @@ -22,6 +34,10 @@ export default function OperatorWorkbench() { .then((s) => setStats(s ?? null)) .catch(() => {}); + actionInboxApi.list({ status: 'pending', page: 1, page_size: 5 }) + .then((r) => setActionItems(r.data)) + .catch(() => {}); + dashboardApi.getPointsRecentActivity() .then((d) => setPointsActivity(d ?? [])) .catch(() => {}); @@ -42,14 +58,6 @@ export default function OperatorWorkbench() { { label: '待审核订单', value: stats?.total_pending ?? 0, color: '#E11D48', trend: '', trendDir: 'down' }, ]; - const todos = [ - { icon: '🎁', bg: '#FFF1F2', color: '#E11D48', title: `审核 ${stats?.total_pending ?? 0} 笔积分兑换订单`, sub: 'AI 标记异常订单需确认', action: '紧急', path: '/health/points-orders' }, - { icon: '📝', bg: '#F0FDF4', color: '#16A34A', title: '发布新科普文章', sub: `已发布 ${articleStats?.published ?? 0} 篇,草稿 ${articleStats?.draft ?? 0} 篇`, action: '发布', path: '/health/articles/new' }, - { icon: '🎪', bg: '#F0F9FF', color: '#0284C7', title: '推送活动报名提醒', sub: '报名截止临近,需推广', action: '推送', path: '/health/offline-events' }, - { icon: '📊', bg: '#F5F3FF', color: '#7C3AED', title: '整理上周运营周报', sub: '数据已就绪', action: '查看', path: '/health/statistics' }, - { icon: '👥', bg: '#FFFBEB', color: '#D97706', title: '跟进沉默用户', sub: '7 天未上报体征,建议关怀', action: '跟进', path: '/health/patients' }, - ]; - return (
{/* AI Hero Card */} @@ -98,22 +106,29 @@ export default function OperatorWorkbench() { navigate('/health/action-inbox')}>全部 →
- {todos.map((todo) => ( -
navigate(todo.path)} - onMouseEnter={(e) => { e.currentTarget.style.background = '#EFF6FF'; }} - onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }} - > -
{todo.icon}
-
-
{todo.title}
-
{todo.sub}
-
-
{todo.action}
-
- ))} + {actionItems.length === 0 ? ( +
暂无待办事项
+ ) : ( + actionItems.map((item) => { + const cfg = TYPE_ICON[item.action_type] ?? TYPE_ICON.alert; + return ( +
navigate('/health/action-inbox')} + onMouseEnter={(e) => { e.currentTarget.style.background = '#EFF6FF'; }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }} + > +
{cfg.icon}
+
+
{item.title}
+
{item.patient_name} · {item.summary}
+
+
{PRIORITY_LABEL[item.priority] ?? '处理'}
+
+ ); + }) + )}