feat(ai): AI 健康管家 V2 基础设施 — 功能开关 + 角色沙箱准备 + 体征页 AI 趋势分析
- 迁移 000153: 新增 ai_feature_flags / ai_usage_daily / ai_suggestion_feedback 三张表, ai_tenant_configs 增加 billing_enabled 列, seed 12 个功能开关 + 2 个管理权限码 - 新增 FeatureFlagService: 5 分钟缓存 + DB 回退 + 即时更新 - VitalSignsTab 添加 AI 趋势分析按钮 (SSE 流式) - 新增 3 个 Entity (ai_feature_flags / ai_usage_daily / ai_suggestion_feedback) - AiState 扩展 feature_flags 字段 - 设计规格 + 讨论记录文档 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { Table, Button, Modal, Form, InputNumber, DatePicker, Input, message, Typography, Tooltip, Popconfirm, Space } from 'antd';
|
||||
import { PlusOutlined, InfoCircleOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { Table, Button, Modal, Form, InputNumber, DatePicker, Input, message, Typography, Tooltip, Popconfirm, Space, Card } from 'antd';
|
||||
import { PlusOutlined, InfoCircleOutlined, EditOutlined, DeleteOutlined, ThunderboltOutlined } from '@ant-design/icons';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import { dayjs } from '../../../utils/dayjs';
|
||||
import { healthDataApi } from '../../../api/health/healthData';
|
||||
@@ -9,6 +9,7 @@ import { VitalSignsChart } from './VitalSignsChart';
|
||||
import { usePaginatedData } from '../../../hooks/usePaginatedData';
|
||||
import { AuthButton } from '../../../components/AuthButton';
|
||||
import { handleApiError } from '../../../api/client';
|
||||
import { startAnalysis } from '../../../api/ai/analysisSse';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -20,6 +21,8 @@ export function VitalSignsTab({ patientId }: Props) {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingRecord, setEditingRecord] = useState<VitalSigns | null>(null);
|
||||
const [chartRefreshKey, setChartRefreshKey] = useState(0);
|
||||
const [analyzingTrend, setAnalyzingTrend] = useState(false);
|
||||
const [trendContent, setTrendContent] = useState('');
|
||||
const [form] = Form.useForm();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
@@ -32,6 +35,16 @@ export function VitalSignsTab({ patientId }: Props) {
|
||||
|
||||
const { data, total, page, loading, refresh } = usePaginatedData<VitalSigns>(fetcher, 10);
|
||||
|
||||
const handleTrendAnalysis = async () => {
|
||||
setAnalyzingTrend(true);
|
||||
setTrendContent('');
|
||||
await startAnalysis('trends', { patient_id: patientId }, {
|
||||
onChunk: (content) => setTrendContent(prev => prev + content),
|
||||
onError: (msg) => { message.error(msg); setAnalyzingTrend(false); },
|
||||
onDone: () => { message.success('AI 趋势分析完成'); setAnalyzingTrend(false); },
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenCreate = () => {
|
||||
setEditingRecord(null);
|
||||
form.resetFields();
|
||||
@@ -211,9 +224,36 @@ export function VitalSignsTab({ patientId }: Props) {
|
||||
<div>
|
||||
{/* 趋势图 */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 8 }}>
|
||||
<AuthButton
|
||||
code="ai.analysis.manage"
|
||||
icon={<ThunderboltOutlined />}
|
||||
loading={analyzingTrend}
|
||||
onClick={handleTrendAnalysis}
|
||||
size="small"
|
||||
>
|
||||
AI 趋势分析
|
||||
</AuthButton>
|
||||
</div>
|
||||
<VitalSignsChart patientId={patientId} refreshKey={chartRefreshKey} />
|
||||
</div>
|
||||
|
||||
{/* AI 趋势分析结果 */}
|
||||
{trendContent && (
|
||||
<Card
|
||||
title={<><ThunderboltOutlined /> AI 趋势分析结果</>}
|
||||
size="small"
|
||||
style={{ marginBottom: 12 }}
|
||||
extra={
|
||||
<Button size="small" onClick={() => setTrendContent('')}>关闭</Button>
|
||||
}
|
||||
>
|
||||
<div style={{ whiteSpace: 'pre-wrap', lineHeight: 1.8 }}>
|
||||
{trendContent}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 最新记录摘要条 */}
|
||||
{latest && (
|
||||
<div style={{
|
||||
|
||||
Reference in New Issue
Block a user