/** * 多指标转换面板组件 * * 功能: * - 方向1:时间点为行,指标为列(统计分析格式) * - 方向2:时间点为列,指标为行(展示格式) * - 自动检测多个指标并分组 */ import React, { useState, useEffect } from 'react'; import { Form, Select, Button, Alert, Table, Spin, Divider, Space, Card, Tag, message, Radio } from 'antd'; import { getAuthHeaders } from '../../../api/toolC'; const { Option } = Select; interface MultiMetricPanelProps { sessionId: string; columns: string[]; onConfirm: (params: any) => void; onCancel: () => void; } interface MetricGrouping { success: boolean; metric_groups?: Record; separator?: string; timepoints?: string[]; confidence?: number; message?: string; } export const MultiMetricPanel: React.FC = ({ sessionId, columns, onConfirm, onCancel, }) => { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [detecting, setDetecting] = useState(false); const [grouping, setGrouping] = useState(null); const [previewData, setPreviewData] = useState([]); const [previewColumns, setPreviewColumns] = useState([]); const [direction, setDirection] = useState<'to_long' | 'to_matrix'>('to_long'); // 转换方向 // 选中的列变化时,自动检测分组 useEffect(() => { const valueVars = form.getFieldValue('valueVars'); if (valueVars && valueVars.length >= 2) { detectGrouping(valueVars); } else { setGrouping(null); setPreviewData([]); setPreviewColumns([]); } }, []); /** * 自动检测多指标分组 */ const detectGrouping = async (valueVars: string[]) => { if (!valueVars || valueVars.length < 2) { setGrouping(null); return; } setDetecting(true); try { console.log(`[MultiMetricPanel] 检测多指标分组: ${valueVars.length} 列`); const response = await fetch('/api/v1/dc/tool-c/multi-metric/detect', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ sessionId, valueVars, }), }); const data = await response.json(); if (data.success) { setGrouping(data.grouping); console.log(`[MultiMetricPanel] 分组检测成功:`, data.grouping); // 自动生成预览 generatePreview( form.getFieldValue('idVars') || [], valueVars, data.grouping ); } else { console.error(`[MultiMetricPanel] 分组检测失败:`, data.error); setGrouping({ success: false, message: data.error || '分组检测失败' }); } } catch (error: any) { console.error(`[MultiMetricPanel] 分组检测异常:`, error); setGrouping({ success: false, message: error.message || '分组检测异常' }); } finally { setDetecting(false); } }; /** * 生成预览数据 */ const generatePreview = async (idVars: string[], valueVars: string[], groupingData: MetricGrouping) => { if (!groupingData.success || !idVars || idVars.length === 0) { return; } setLoading(true); try { // 根据转换方向调用不同的API const action = direction === 'to_long' ? 'multi_metric_to_long' : 'multi_metric_to_matrix'; const params = direction === 'to_long' ? { idVars, valueVars, eventColName: form.getFieldValue('eventColName') || 'Event_Name', } : { idVars, valueVars, metricColName: form.getFieldValue('metricColName') || '指标名', }; // 调用preview API const response = await fetch('/api/v1/dc/tool-c/quick-action/preview', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ sessionId, action, params, }), }); const data = await response.json(); if (data.success) { const resultData = data.data.newDataPreview || []; if (resultData.length > 0) { // 生成列定义 const cols = Object.keys(resultData[0]).map((key) => ({ title: key, dataIndex: key, key, width: 150, })); setPreviewColumns(cols); setPreviewData(resultData.slice(0, 10)); // 只显示前10行 } } else { console.error(`[MultiMetricPanel] 预览失败:`, data.error); } } catch (error: any) { console.error(`[MultiMetricPanel] 预览异常:`, error); } finally { setLoading(false); } }; /** * 处理表单提交 */ const handleSubmit = async () => { try { const values = await form.validateFields(); if (!grouping || !grouping.success) { message.error('请先检测指标分组'); return; } setLoading(true); console.log(`[MultiMetricPanel] 提交多指标转换 (${direction}):`, values); // 根据转换方向调用不同的API const action = direction === 'to_long' ? 'multi_metric_to_long' : 'multi_metric_to_matrix'; const params = direction === 'to_long' ? { idVars: values.idVars, valueVars: values.valueVars, eventColName: values.eventColName || 'Event_Name', } : { idVars: values.idVars, valueVars: values.valueVars, metricColName: values.metricColName || '指标名', }; // 调用快速操作API执行转换 const response = await fetch('/api/v1/dc/tool-c/quick-action', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ sessionId, action, params, }), }); const result = await response.json(); if (!result.success) { throw new Error(result.error || '多指标转换失败'); } const successMsg = direction === 'to_long' ? '多指标转长表成功!' : '多指标转矩阵成功!'; message.success(successMsg); // 更新父组件数据 if (result.data?.newDataPreview) { onConfirm(result.data.newDataPreview); } // 成功后关闭 onCancel(); } catch (error: any) { console.error(`[MultiMetricPanel] 转换失败:`, error); message.error(error.message || '执行失败'); } finally { setLoading(false); } }; /** * 处理值列变化 */ const handleValueVarsChange = (valueVars: string[]) => { form.setFieldsValue({ valueVars }); detectGrouping(valueVars); }; /** * 处理ID列变化 */ const handleIdVarsChange = (idVars: string[]) => { form.setFieldsValue({ idVars }); // 重新生成预览 const valueVars = form.getFieldValue('valueVars'); if (grouping && grouping.success && valueVars && valueVars.length >= 2) { generatePreview(idVars, valueVars, grouping); } }; return (
{/* 0. 转换方向 */}
📍 转换方向:
{ setDirection(e.target.value); // 重新生成预览 const idVars = form.getFieldValue('idVars'); const valueVars = form.getFieldValue('valueVars'); if (grouping && grouping.success && idVars && valueVars && valueVars.length >= 2) { generatePreview(idVars, valueVars, grouping); } }} style={{ width: '100%' }} >
时间点→行,指标→列 分析格式
适用于:统计分析、混合效应模型、GEE、数据可视化
时间点→列,指标→行 展示格式
适用于:临床报告、数据审查表、CRF核对、单受试者数据审查
{/* 1. 选择ID列 */} {/* 2. 选择值列 */} 2️⃣ 选择值列(自动分组)
} name="valueVars" rules={[ { required: true, message: '请选择至少2个值列' }, { validator: (_, value) => { if (value && value.length >= 2) { return Promise.resolve(); } return Promise.reject(new Error('至少需要选择2列才能进行转换')); }, }, ]} extra="选择多个指标的多个时间点列(例如:FMA总得分_基线、FMA总得分_随访1、ADL总分_基线、ADL总分_随访1)" > {/* 3. 自动检测结果 */} {detecting && (
)} {!detecting && grouping && ( {grouping.success ? (
✓ 检测到 {Object.keys(grouping.metric_groups || {}).length} 个指标:
{Object.entries(grouping.metric_groups || {}).map(([metric, cols]) => ( {metric} ({cols.length}列) ))}
✓ 检测到 {grouping.timepoints?.length || 0} 个时间点:
{grouping.timepoints?.slice(0, 5).map((tp) => ( {tp} ))} {(grouping.timepoints?.length || 0) > 5 && ( ... 还有 {(grouping.timepoints?.length || 0) - 5} 个 )}
✓ 分隔符: {grouping.separator || '(无)'}
{grouping.confidence !== undefined && grouping.confidence < 1.0 && ( )}
) : ( )}
)} {/* 4. 列名设置 */} {direction === 'to_long' ? ( ) : ( )} {/* 5. 预览结果 */} {previewData.length > 0 && ( <> 预览结果(前10行)
)} {/* 操作按钮 */}
); };