feat(web): Home.tsx 集成统一工作台 — 医生行动收件箱 + 主任团队概览
- 医生/护士角色:待办任务行替换为行动收件箱(TodoList) + AI 概览面板 - 主任角色:在最近动态下方新增 TeamOverviewPanel 团队概览 - 所有角色:点击待办项可打开 ActionDetailDrawer 查看详情和操作 - admin/operator 角色保持原有待办任务+最近动态布局
This commit is contained in:
@@ -30,6 +30,11 @@ import { listPendingTasks, type TaskInfo } from '../api/workflowTasks';
|
|||||||
import { pointsApi, type PersonalStats } from '../api/health/points';
|
import { pointsApi, type PersonalStats } from '../api/health/points';
|
||||||
import { useStatsData } from './health/StatisticsDashboard/useStatsData';
|
import { useStatsData } from './health/StatisticsDashboard/useStatsData';
|
||||||
import { useCountUp } from '../hooks/useCountUp';
|
import { useCountUp } from '../hooks/useCountUp';
|
||||||
|
import TodoList from './health/components/workbench/TodoList';
|
||||||
|
import AiInsightPanel from './health/components/workbench/AiInsightPanel';
|
||||||
|
import TeamOverviewPanel from './health/components/workbench/TeamOverviewPanel';
|
||||||
|
import ActionDetailDrawer from './health/components/workbench/ActionDetailDrawer';
|
||||||
|
import type { ActionItem } from '../api/health/actionInbox';
|
||||||
|
|
||||||
// --- Shared utilities ---
|
// --- Shared utilities ---
|
||||||
|
|
||||||
@@ -172,6 +177,8 @@ export default function Home() {
|
|||||||
const [pendingTasks, setPendingTasks] = useState<TaskInfo[]>([]);
|
const [pendingTasks, setPendingTasks] = useState<TaskInfo[]>([]);
|
||||||
const [recentActivities, setRecentActivities] = useState<AuditLogItem[]>([]);
|
const [recentActivities, setRecentActivities] = useState<AuditLogItem[]>([]);
|
||||||
const [activitiesLoading, setActivitiesLoading] = useState(true);
|
const [activitiesLoading, setActivitiesLoading] = useState(true);
|
||||||
|
const [drawerItem, setDrawerItem] = useState<ActionItem | null>(null);
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
|
||||||
const statsData = useStatsData();
|
const statsData = useStatsData();
|
||||||
const loading = personalLoading || statsData.loading;
|
const loading = personalLoading || statsData.loading;
|
||||||
@@ -262,75 +269,94 @@ export default function Home() {
|
|||||||
|
|
||||||
{/* 待办任务 + 最近活动 */}
|
{/* 待办任务 + 最近活动 */}
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||||
<Col xs={24} lg={14}>
|
{(role === 'doctor' || role === 'nurse') ? (
|
||||||
<div className="erp-content-card erp-fade-in erp-fade-in-delay-2">
|
<>
|
||||||
<div className="erp-section-header">
|
<Col xs={24} lg={14}>
|
||||||
<CheckCircleOutlined className="erp-section-icon" />
|
<div className="erp-content-card erp-fade-in erp-fade-in-delay-2">
|
||||||
<span className="erp-section-title">待办任务</span>
|
<div className="erp-section-header">
|
||||||
<span style={{ marginLeft: 'auto', fontSize: 12, color: 'var(--erp-text-secondary)' }}>
|
<CheckCircleOutlined className="erp-section-icon" />
|
||||||
{pendingTasks.length} 项待处理
|
<span className="erp-section-title">行动收件箱</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
<TodoList onItemClick={(item) => { setDrawerItem(item); setDrawerOpen(true); }} />
|
||||||
<div className="erp-task-list">
|
</div>
|
||||||
{pendingTasks.length === 0 ? (
|
</Col>
|
||||||
<Empty description="暂无待办任务" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
<Col xs={24} lg={10}>
|
||||||
) : (
|
<AiInsightPanel />
|
||||||
pendingTasks.map((task) => (
|
</Col>
|
||||||
<div
|
</>
|
||||||
key={task.id}
|
) : (
|
||||||
className="erp-task-item"
|
<>
|
||||||
style={{ '--task-color': 'var(--erp-primary)' } as React.CSSProperties}
|
<Col xs={24} lg={14}>
|
||||||
onClick={() => handleNavigate('/workflow')}
|
<div className="erp-content-card erp-fade-in erp-fade-in-delay-2">
|
||||||
role="button"
|
<div className="erp-section-header">
|
||||||
tabIndex={0}
|
<CheckCircleOutlined className="erp-section-icon" />
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate('/workflow'); }}
|
<span className="erp-section-title">待办任务</span>
|
||||||
>
|
<span style={{ marginLeft: 'auto', fontSize: 12, color: 'var(--erp-text-secondary)' }}>
|
||||||
<div className="erp-task-item-icon"><PartitionOutlined /></div>
|
{pendingTasks.length} 项待处理
|
||||||
<div className="erp-task-item-content">
|
</span>
|
||||||
<div className="erp-task-item-title">{task.node_name || task.definition_name || '流程任务'}</div>
|
</div>
|
||||||
<div className="erp-task-item-meta">
|
<div className="erp-task-list">
|
||||||
<span>{task.definition_name || '工作流'}</span>
|
{pendingTasks.length === 0 ? (
|
||||||
<span>{task.status === 'pending' ? '待处理' : task.status}</span>
|
<Empty description="暂无待办任务" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
) : (
|
||||||
|
pendingTasks.map((task) => (
|
||||||
|
<div
|
||||||
|
key={task.id}
|
||||||
|
className="erp-task-item"
|
||||||
|
style={{ '--task-color': 'var(--erp-primary)' } as React.CSSProperties}
|
||||||
|
onClick={() => handleNavigate('/workflow')}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate('/workflow'); }}
|
||||||
|
>
|
||||||
|
<div className="erp-task-item-icon"><PartitionOutlined /></div>
|
||||||
|
<div className="erp-task-item-content">
|
||||||
|
<div className="erp-task-item-title">{task.node_name || task.definition_name || '流程任务'}</div>
|
||||||
|
<div className="erp-task-item-meta">
|
||||||
|
<span>{task.definition_name || '工作流'}</span>
|
||||||
|
<span>{task.status === 'pending' ? '待处理' : task.status}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="erp-task-priority erp-task-priority-medium">一般</span>
|
||||||
|
<RightOutlined style={{ color: 'var(--erp-text-tertiary)', fontSize: 12 }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))
|
||||||
<span className="erp-task-priority erp-task-priority-medium">一般</span>
|
)}
|
||||||
<RightOutlined style={{ color: 'var(--erp-text-tertiary)', fontSize: 12 }} />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
</Col>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={24} lg={10}>
|
<Col xs={24} lg={10}>
|
||||||
<div className="erp-content-card erp-fade-in erp-fade-in-delay-3" style={{ height: '100%' }}>
|
<div className="erp-content-card erp-fade-in erp-fade-in-delay-3" style={{ height: '100%' }}>
|
||||||
<div className="erp-section-header">
|
<div className="erp-section-header">
|
||||||
<ClockCircleOutlined className="erp-section-icon" />
|
<ClockCircleOutlined className="erp-section-icon" />
|
||||||
<span className="erp-section-title">最近动态</span>
|
<span className="erp-section-title">最近动态</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="erp-activity-list">
|
<div className="erp-activity-list">
|
||||||
{activitiesLoading ? (
|
{activitiesLoading ? (
|
||||||
<div style={{ textAlign: 'center', padding: 24 }}><Spin /></div>
|
<div style={{ textAlign: 'center', padding: 24 }}><Spin /></div>
|
||||||
) : recentActivities.length === 0 ? (
|
) : recentActivities.length === 0 ? (
|
||||||
<Empty description="暂无动态" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
<Empty description="暂无动态" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
) : (
|
) : (
|
||||||
recentActivities.map((log) => (
|
recentActivities.map((log) => (
|
||||||
<div key={log.id} className="erp-activity-item">
|
<div key={log.id} className="erp-activity-item">
|
||||||
<div className="erp-activity-dot">
|
<div className="erp-activity-dot">
|
||||||
{RESOURCE_ICONS[log.resource_type] || <FileTextOutlined />}
|
{RESOURCE_ICONS[log.resource_type] || <FileTextOutlined />}
|
||||||
</div>
|
</div>
|
||||||
<div className="erp-activity-content">
|
<div className="erp-activity-content">
|
||||||
<div className="erp-activity-text">
|
<div className="erp-activity-text">
|
||||||
{formatActionLabel(log.action)}了{formatResourceLabel(log.resource_type)}
|
{formatActionLabel(log.action)}了{formatResourceLabel(log.resource_type)}
|
||||||
|
</div>
|
||||||
|
<div className="erp-activity-time">{formatTimeAgo(log.created_at)}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="erp-activity-time">{formatTimeAgo(log.created_at)}</div>
|
))
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
</div>
|
||||||
)}
|
</Col>
|
||||||
</div>
|
</>
|
||||||
</div>
|
)}
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{/* 快捷入口 */}
|
{/* 快捷入口 */}
|
||||||
@@ -361,6 +387,26 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* 主任团队概览 */}
|
||||||
|
{role === 'admin' && (
|
||||||
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||||
|
<Col span={24}>
|
||||||
|
<TeamOverviewPanel />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 行动详情抽屉 */}
|
||||||
|
<ActionDetailDrawer
|
||||||
|
item={drawerItem}
|
||||||
|
open={drawerOpen}
|
||||||
|
onClose={() => { setDrawerOpen(false); setDrawerItem(null); }}
|
||||||
|
onActionComplete={() => {
|
||||||
|
setDrawerOpen(false);
|
||||||
|
setDrawerItem(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user