fix(health): 客户试用前全局审计修复 — P0 权限旁路 + API 路径 + 事件注册
P0 阻塞修复:
- 修复 PrivateRoute 权限旁路: p.startsWith('auth.') 匹配不到任何权限码,
改为基于实际权限码的路由级检查 (user.manage/role.manage/organization.manage)
- 修复 deviceReadings API 路径: /patients/{id}/device-readings/daily 改为
/vital-signs/daily?patient_id=, 消除 404
P1 重要修复:
- 补全事件注册表: 新增 auth(11) + config(8) + workflow(4) + plugin(2) = 25 条
- article_article_tag 联表新增 tenant_id + deleted_at + 审计列 (迁移 107)
- vital_signs_hourly 新增 deleted_at 支持软删除过滤 (迁移 108)
- 6 个页面添加权限守卫 (AlertDashboard/AlertRuleList/DeviceManage/
AiAnalysisList/AiUsageDashboard)
- DialysisModule 声明 auth 依赖
This commit is contained in:
@@ -68,9 +68,16 @@ function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// 路由级权限检查:如果用户对某个模块完全没有权限,重定向到首页
|
||||
const path = window.location.hash.replace('#', '');
|
||||
if (path.startsWith('/users') || path.startsWith('/roles') || path.startsWith('/organizations')) {
|
||||
const hasAuthAccess = permissions.some((p) => p.startsWith('auth.'));
|
||||
if (!hasAuthAccess) return <Navigate to="/" replace />;
|
||||
const routePermissions: Record<string, string[]> = {
|
||||
'/users': ['user.list', 'user.manage'],
|
||||
'/roles': ['role.list', 'role.manage'],
|
||||
'/organizations': ['organization.list', 'organization.manage'],
|
||||
};
|
||||
const matchedPrefix = Object.keys(routePermissions).find((prefix) => path.startsWith(prefix));
|
||||
if (matchedPrefix) {
|
||||
const required = routePermissions[matchedPrefix];
|
||||
const hasAccess = permissions.some((p) => required.some((r) => p === r || p.startsWith(r.split('.')[0] + '.')));
|
||||
if (!hasAccess) return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
|
||||
@@ -67,6 +67,6 @@ export const deviceReadingApi = {
|
||||
|
||||
queryDaily: (params: { patient_id: string; device_type?: string; from_date?: string; to_date?: string; page?: number; page_size?: number }) => {
|
||||
const { patient_id, ...query } = params;
|
||||
return client.get(`/health/patients/${patient_id}/device-readings/daily`, { params: query }).then((r) => r.data.data as PaginatedResponse<DailyReading>);
|
||||
return client.get(`/health/vital-signs/daily`, { params: { ...query, patient_id } }).then((r) => r.data.data as PaginatedResponse<DailyReading>);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useSearchParams, Link } from 'react-router-dom';
|
||||
import { Table, Select, Tag, Space, Button, message, Typography } from 'antd';
|
||||
import { Table, Select, Tag, Space, Button, message, Result, Typography } from 'antd';
|
||||
import {
|
||||
RobotOutlined,
|
||||
CheckCircleOutlined,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
WarningOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { analysisApi, type AnalysisItem } from '../../api/ai/analysis';
|
||||
import { suggestionApi, type SuggestionItem } from '../../api/ai/suggestions';
|
||||
import { EntityName } from '../../components/EntityName';
|
||||
@@ -249,6 +250,8 @@ function SuggestionPanel({ analysisId, isDark }: { analysisId: string; isDark: b
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function AiAnalysisList() {
|
||||
const { hasPermission } = usePermission('ai.analysis.list');
|
||||
if (!hasPermission) return <Result status="403" title="权限不足" subTitle="您没有查看 AI 分析的权限" />;
|
||||
const [searchParams] = useSearchParams();
|
||||
const urlPatientId = searchParams.get('patient_id');
|
||||
const [data, setData] = useState<AnalysisItem[]>([]);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card, Spin, Statistic, message, Empty, Row, Col } from 'antd';
|
||||
import { Card, Spin, Statistic, message, Empty, Result, Row, Col } from 'antd';
|
||||
import {
|
||||
ThunderboltOutlined,
|
||||
ExperimentOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { usageApi, type UsageOverview, type TypeDistribution } from '../../api/ai/usage';
|
||||
|
||||
const ANALYSIS_TYPE_MAP: Record<string, string> = {
|
||||
@@ -22,6 +23,8 @@ const TYPE_COLORS: Record<string, string> = {
|
||||
};
|
||||
|
||||
export default function AiUsageDashboard() {
|
||||
const { hasPermission } = usePermission('ai.usage.list');
|
||||
if (!hasPermission) return <Result status="403" title="权限不足" subTitle="您没有查看 AI 用量的权限" />;
|
||||
const [overview, setOverview] = useState<UsageOverview | null>(null);
|
||||
const [types, setTypes] = useState<TypeDistribution[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Spin,
|
||||
Space,
|
||||
Flex,
|
||||
Result,
|
||||
} from 'antd';
|
||||
import {
|
||||
AlertOutlined,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
WifiOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { alertApi, type Alert } from '../../api/health/alerts';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import { SEVERITY_COLOR, SEVERITY_LABEL, ALERT_STATUS_COLOR, ALERT_STATUS_LABEL, ALERT_STATUS_OPTIONS } from '../../constants/health';
|
||||
import { useAlertSSE, type AlertSSEEvent } from '../../hooks/useAlertSSE';
|
||||
import { AlertDetailPanel } from './components/AlertDetailPanel';
|
||||
@@ -38,6 +40,8 @@ import { EntityName } from '../../components/EntityName';
|
||||
* - 确认/忽略/恢复操作
|
||||
*/
|
||||
export default function AlertDashboard() {
|
||||
const { hasPermission } = usePermission('health.alerts.list');
|
||||
if (!hasPermission) return <Result status="403" title="权限不足" subTitle="您没有查看告警面板的权限" />;
|
||||
const [alerts, setAlerts] = useState<Alert[]>([]);
|
||||
const [selectedAlert, setSelectedAlert] = useState<Alert | null>(null);
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, InputNumber, message, Modal, Select, Space, Switch, Table, Tag } from 'antd';
|
||||
import { Button, Form, Input, InputNumber, message, Modal, Result, Select, Space, Switch, Table, Tag } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
|
||||
import {
|
||||
@@ -9,8 +9,11 @@ import {
|
||||
type UpdateAlertRuleReq,
|
||||
} from '../../api/health/alerts';
|
||||
import { SEVERITY_COLOR, SEVERITY_OPTIONS, DEVICE_TYPE_OPTIONS, CONDITION_TYPE_OPTIONS } from '../../constants/health';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
|
||||
export default function AlertRuleList() {
|
||||
const { hasPermission } = usePermission('health.alerts.list');
|
||||
if (!hasPermission) return <Result status="403" title="权限不足" subTitle="您没有查看告警规则的权限" />;
|
||||
const [data, setData] = useState<AlertRule[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, Input, message, Popconfirm, Select, Space, Table, Tag, Badge } from 'antd';
|
||||
import { Button, Input, message, Popconfirm, Result, Select, Space, Table, Tag, Badge } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { deviceApi, type DeviceItem } from '../../api/health/devices';
|
||||
import { DEVICE_TYPE_OPTIONS, DEVICE_TYPE_COLOR, DEVICE_STATUS_OPTIONS } from '../../constants/health';
|
||||
import { PatientSelect } from './components/PatientSelect';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
|
||||
function formatTime(val?: string | null): string {
|
||||
if (!val) return '-';
|
||||
@@ -13,6 +14,8 @@ function formatTime(val?: string | null): string {
|
||||
}
|
||||
|
||||
export default function DeviceManage() {
|
||||
const { hasPermission } = usePermission('health.devices.list');
|
||||
if (!hasPermission) return <Result status="403" title="权限不足" subTitle="您没有管理设备的权限" />;
|
||||
const [data, setData] = useState<DeviceItem[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user