新增 usePageData hook(useDidShow 节流 + usePullDownRefresh + loadingRef 防重入 + enabled 条件守卫), 44/58 页面迁移接入,消灭 4 种数据加载模式并存。 - 新增 hooks/usePageData.ts — 统一页面数据加载生命周期 - 新增 stores/index.ts — resetAllStores() 解耦 auth↔health store 依赖 - 新增 pages/index/useHomeData.ts — 首页数据 hook(424→282 行) - 新增 pages/health/useHealthData.ts — 健康页数据 hook(422→254 行) - 44 个页面迁移到 usePageData(9 患者端 + 15 医生端 + 20 子包) - auth store logout 不再直接导入 health store 构建通过,测试 74/75(1 个预存失败)。
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
import React, { useState, useCallback } from 'react';
|
||
import { View, Text, RichText } from '@tarojs/components';
|
||
import Taro, { useRouter } from '@tarojs/taro';
|
||
import { usePageData } from '@/hooks/usePageData';
|
||
import { getAiAnalysisDetail, type AiAnalysisItem } from '@/services/ai-analysis';
|
||
import Loading from '@/components/Loading';
|
||
import { sanitizeHtml } from '@/utils/sanitize-html';
|
||
import { useElderClass } from '../../../hooks/useElderClass';
|
||
import './index.scss';
|
||
|
||
const TYPE_LABELS: Record<string, string> = {
|
||
lab_report_interpretation: '化验单解读',
|
||
health_trend_analysis: '趋势分析',
|
||
personalized_checkup_plan: '体检方案',
|
||
report_summary_generation: '报告摘要',
|
||
};
|
||
|
||
function markdownToHtml(md: string): string {
|
||
const escaped = sanitizeHtml(md);
|
||
return escaped
|
||
.replace(/^(#{1,3}) (.+)$/gm, (_, h: string, t: string) => `<h${h.length}>${t}</h${h.length}>`)
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||
.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>')
|
||
.replace(/\n\n/g, '<br/><br/>')
|
||
.replace(/\n/g, '<br/>');
|
||
}
|
||
|
||
export default function AiReportDetail() {
|
||
const modeClass = useElderClass();
|
||
const router = useRouter();
|
||
const id = router.params.id || '';
|
||
|
||
const [analysis, setAnalysis] = useState<AiAnalysisItem | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const fetchDetail = useCallback(async () => {
|
||
if (!id) return;
|
||
setLoading(true);
|
||
try {
|
||
const data = await getAiAnalysisDetail(id);
|
||
setAnalysis(data);
|
||
} catch {
|
||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [id]);
|
||
|
||
usePageData(fetchDetail, { throttleMs: 60000 });
|
||
|
||
if (loading) return <Loading />;
|
||
|
||
if (!analysis) {
|
||
return (
|
||
<View className={`detail-page ${modeClass}`}>
|
||
<Text className='empty-text'>报告不存在</Text>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const htmlContent = analysis.result_content
|
||
? markdownToHtml(analysis.result_content)
|
||
: '<p>暂无分析结果</p>';
|
||
|
||
const isTrendAnalysis = analysis.analysis_type === 'trend';
|
||
const isAutoAnalysis = (analysis.result_metadata as Record<string, unknown>)?.auto_analysis === true;
|
||
|
||
return (
|
||
<View className={`detail-page ${modeClass}`}>
|
||
<View className='detail-card'>
|
||
<Text className='detail-type'>{TYPE_LABELS[analysis.analysis_type] || analysis.analysis_type}</Text>
|
||
<View className='detail-meta'>
|
||
<Text className='meta-item'>模型: {analysis.model_used}</Text>
|
||
<Text className='meta-item'>{new Date(analysis.created_at).toLocaleString('zh-CN')}</Text>
|
||
</View>
|
||
{isAutoAnalysis && (
|
||
<View className='auto-badge'>
|
||
<Text className='auto-badge-text'>系统自动分析</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{isTrendAnalysis && (
|
||
<View className='trend-tip-card'>
|
||
<Text className='trend-tip-text'>
|
||
趋势分析基于最小二乘法线性回归和 2 倍标准差异常检测。R² 越接近 1 表示趋势拟合越好。斜率为正表示上升趋势,斜率为负表示下降趋势。
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
<View className='content-card'>
|
||
<RichText className='report-content' nodes={htmlContent} />
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|