fix(web): 统计页空列表接入真实 API + 运营待办去硬编码
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- DoctorDashboard: 咨询消息接入 consultationApi.listSessions
- NurseDashboard: 随访队列接入 followUpApi.listTasks
- OperatorDashboard: 热门文章接入 articleApi.list
- OperatorWorkbench: 5 条硬编码待办替换为 actionInboxApi 真实数据
This commit is contained in:
iven
2026-05-03 00:02:58 +08:00
parent 603af83aa9
commit 2e4d98c479
4 changed files with 112 additions and 34 deletions

View File

@@ -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<string, { icon: string; bg: string; color: string }> = {
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<string, string> = {
urgent: '紧急', high: '高', medium: '中', low: '低',
};
export default function OperatorWorkbench() {
const navigate = useNavigate();
const user = useAuthStore((s) => s.user);
const [stats, setStats] = useState<WorkbenchStats | null>(null);
const [actionItems, setActionItems] = useState<ActionItem[]>([]);
const [pointsActivity, setPointsActivity] = useState<PointsActivityItem[]>([]);
const [articleStats, setArticleStats] = useState<ArticleStatsResp | null>(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 (
<div>
{/* AI Hero Card */}
@@ -98,22 +106,29 @@ export default function OperatorWorkbench() {
<span style={{ fontSize: 11, color: '#2563EB', cursor: 'pointer' }} onClick={() => navigate('/health/action-inbox')}> </span>
</div>
<div>
{todos.map((todo) => (
<div
key={todo.path}
style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 18px', borderBottom: '1px solid #F1F5F9', cursor: 'pointer', transition: 'all 0.15s' }}
onClick={() => navigate(todo.path)}
onMouseEnter={(e) => { e.currentTarget.style.background = '#EFF6FF'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
>
<div style={{ width: 28, height: 28, borderRadius: 6, background: todo.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 13, flexShrink: 0 }}>{todo.icon}</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 500 }}>{todo.title}</div>
<div style={{ fontSize: 11, color: '#94A3B8' }}>{todo.sub}</div>
</div>
<div style={{ fontSize: 11, padding: '3px 10px', borderRadius: 4, border: '1px solid #E2E8F0', color: '#475569', cursor: 'pointer', flexShrink: 0 }}>{todo.action}</div>
</div>
))}
{actionItems.length === 0 ? (
<div style={{ padding: 20, textAlign: 'center', color: '#94A3B8', fontSize: 13 }}></div>
) : (
actionItems.map((item) => {
const cfg = TYPE_ICON[item.action_type] ?? TYPE_ICON.alert;
return (
<div
key={item.id}
style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 18px', borderBottom: '1px solid #F1F5F9', cursor: 'pointer', transition: 'all 0.15s' }}
onClick={() => navigate('/health/action-inbox')}
onMouseEnter={(e) => { e.currentTarget.style.background = '#EFF6FF'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
>
<div style={{ width: 28, height: 28, borderRadius: 6, background: cfg.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 13, flexShrink: 0 }}>{cfg.icon}</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 500 }}>{item.title}</div>
<div style={{ fontSize: 11, color: '#94A3B8' }}>{item.patient_name} · {item.summary}</div>
</div>
<div style={{ fontSize: 11, padding: '3px 10px', borderRadius: 4, border: '1px solid #E2E8F0', color: '#475569', cursor: 'pointer', flexShrink: 0 }}>{PRIORITY_LABEL[item.priority] ?? '处理'}</div>
</div>
);
})
)}
</div>
</div>