fix(health): 审计问题修复 — 权限守卫 + OAuth中间件 + FHIR声明 + SSE聚合
- OAuthClientList/RealtimeMonitor/OfflineEventList/StatisticsDashboard 补权限守卫 - OAuth 中间件注入 TenantContext + FHIR scope→permission 映射 - FHIR CapabilityStatement 移除未实现的 $lastn 操作 - useVitalSSE 修复批量同步事件数据聚合逻辑
This commit is contained in:
@@ -41,15 +41,17 @@ export function useVitalSSE(options: UseVitalSSEOptions = {}): UseVitalSSEReturn
|
||||
|
||||
setPatientVitals((prev) => {
|
||||
const next = new Map(prev);
|
||||
if (data.device_model) {
|
||||
const key = `${data.patient_id}_${data.device_model}`;
|
||||
next.set(key, {
|
||||
patient_id: data.patient_id,
|
||||
device_type: data.device_model,
|
||||
latest_value: data.count > 0 ? undefined : undefined,
|
||||
updated_at: data.occurred_at ?? new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
const key = `${data.patient_id}_${data.device_model ?? 'unknown'}`;
|
||||
const existing = next.get(key);
|
||||
next.set(key, {
|
||||
patient_id: data.patient_id,
|
||||
device_type: data.device_model ?? 'unknown',
|
||||
// 批量同步事件不含单值,用累计 count 表示活跃度
|
||||
latest_value: data.count > 0
|
||||
? (existing?.latest_value ?? 0) + data.count
|
||||
: existing?.latest_value,
|
||||
updated_at: data.occurred_at ?? new Date().toISOString(),
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setLastUpdate(data);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { Button, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography } from 'antd';
|
||||
import { Button, Form, Input, InputNumber, message, Modal, Popconfirm, Result, Select, Space, Switch, Table, Tag, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -11,8 +11,10 @@ import {
|
||||
FHIR_SCOPE_OPTIONS,
|
||||
} from '../../api/health/oauthClients';
|
||||
import { PageContainer } from '../../components/PageContainer';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
|
||||
export default function OAuthClientList() {
|
||||
const { hasPermission } = usePermission('health.oauth.manage');
|
||||
const [data, setData] = useState<OAuthClient[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@@ -162,6 +164,10 @@ export default function OAuthClientList() {
|
||||
},
|
||||
];
|
||||
|
||||
if (!hasPermission) {
|
||||
return <Result status="403" title="权限不足" subTitle="您没有管理 FHIR API 合作方的权限" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="FHIR API 合作方管理"
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
type CreateOfflineEventReq,
|
||||
} from '../../api/health/points';
|
||||
import { AuthButton } from '../../components/AuthButton';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { Result } from 'antd';
|
||||
|
||||
/** 活动状态映射 */
|
||||
const STATUS_MAP: Record<string, { text: string; color: string }> = {
|
||||
@@ -48,6 +50,7 @@ const STATUS_OPTIONS = Object.entries(STATUS_MAP).map(([value, { text }]) => ({
|
||||
}));
|
||||
|
||||
export default function OfflineEventList() {
|
||||
const { hasPermission } = usePermission('health.points.list');
|
||||
const [data, setData] = useState<OfflineEvent[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -58,6 +61,10 @@ export default function OfflineEventList() {
|
||||
const [editing, setEditing] = useState<OfflineEvent | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
if (!hasPermission) {
|
||||
return <Result status="403" title="权限不足" subTitle="您没有访问线下活动管理的权限" />;
|
||||
}
|
||||
|
||||
// ---- 数据获取 ----
|
||||
const fetchData = useCallback(async (p = page, ps = pageSize) => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { Card, Row, Col, Statistic, Tag, List, Select, Badge, Typography, Space, Empty } from 'antd';
|
||||
import { Card, Row, Col, Statistic, Tag, List, Select, Badge, Typography, Space, Empty, Result } from 'antd';
|
||||
import { AlertOutlined } from '@ant-design/icons';
|
||||
import { useVitalSSE } from '../../hooks/useVitalSSE';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { alertApi, type Alert } from '../../api/health/alerts';
|
||||
import { PageContainer } from '../../components/PageContainer';
|
||||
import { SEVERITY_COLOR, SEVERITY_LABEL, VITAL_CARD_METRICS } from '../../constants/health';
|
||||
@@ -20,6 +21,7 @@ interface PatientAlertSummary {
|
||||
* SSE 实时接收体征更新,按告警严重度排序患者列表。
|
||||
*/
|
||||
export default function RealtimeMonitor() {
|
||||
const { hasPermission } = usePermission('health.alerts.list');
|
||||
const [alerts, setAlerts] = useState<Alert[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedPatientId, setSelectedPatientId] = useState<string | null>(null);
|
||||
@@ -68,6 +70,10 @@ export default function RealtimeMonitor() {
|
||||
const totalMedium = alertSummary.reduce((s, a) => s + a.medium, 0);
|
||||
const totalLow = alertSummary.reduce((s, a) => s + a.low, 0);
|
||||
|
||||
if (!hasPermission) {
|
||||
return <Result status="403" title="权限不足" subTitle="您没有访问实时体征监控的权限" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="实时体征监控"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useDashboardRole } from '../../hooks/useDashboardRole';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { Result } from 'antd';
|
||||
import { DoctorDashboard } from './StatisticsDashboard/DoctorDashboard';
|
||||
import { NurseDashboard } from './StatisticsDashboard/NurseDashboard';
|
||||
import { AdminDashboard } from './StatisticsDashboard/AdminDashboard';
|
||||
@@ -13,7 +15,13 @@ const DASHBOARD_MAP: Record<string, React.FC> = {
|
||||
};
|
||||
|
||||
export default function StatisticsDashboard() {
|
||||
const { hasPermission } = usePermission('health.dashboard.manage');
|
||||
const role = useDashboardRole();
|
||||
|
||||
if (!hasPermission) {
|
||||
return <Result status="403" title="权限不足" subTitle="您没有访问统计面板的权限" />;
|
||||
}
|
||||
|
||||
const DashboardComponent = DASHBOARD_MAP[role];
|
||||
return <DashboardComponent />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user