# SSA-Pro 前端开发指南 > **文档版本:** v1.5 > **创建日期:** 2026-02-18 > **最后更新:** 2026-02-18(纳入专家配置体系 + 护栏 Action 展示) > **目标读者:** 前端工程师 > **原型参考:** `03-UI设计/智能统计分析V2.html` --- ## 1. 模块目录结构 ``` frontend-v2/src/modules/ssa/ ├── index.ts # 模块入口,导出路由 ├── pages/ │ └── SSAWorkspace.tsx # 主页面(工作区) ├── components/ │ ├── layout/ │ │ ├── SSASidebar.tsx # 左侧边栏 │ │ ├── SSAHeader.tsx # 顶部标题栏 │ │ ├── SSAInputArea.tsx # 底部输入区 │ │ └── ModeSwitch.tsx # 🆕 模式切换 Tab │ ├── chat/ │ │ ├── MessageList.tsx # 消息流容器 │ │ ├── SystemMessage.tsx # 系统消息气泡 │ │ ├── UserMessage.tsx # 用户消息气泡 │ │ └── AssistantMessage.tsx # AI 消息(含卡片) │ ├── cards/ │ │ ├── DataUploader.tsx # 数据上传区 │ │ ├── DataStatus.tsx # 数据集状态卡片 │ │ ├── PlanCard.tsx # 分析计划确认卡片 ⭐ │ │ ├── ExecutionTrace.tsx # 执行路径树 ⭐ │ │ ├── ExecutionProgress.tsx# 📌 执行进度动画 ⭐ │ │ ├── ResultCard.tsx # 结果报告卡片 ⭐ │ │ └── SAPPreview.tsx # 🆕 SAP 文档预览/下载 │ ├── consult/ # 🆕 咨询模式组件 │ │ ├── ConsultChat.tsx # 无数据对话界面 │ │ └── SAPDownloadButton.tsx# SAP 下载按钮 │ └── common/ │ ├── APATable.tsx # 三线表组件 │ └── PlotViewer.tsx # 图表查看器 ├── hooks/ │ ├── useSSASession.ts # 会话管理 Hook │ ├── useSSAExecution.ts # 执行控制 Hook │ └── useSSAConsult.ts # 🆕 咨询模式 Hook ├── store/ │ └── ssaStore.ts # Zustand Store(含 mode 状态) ├── api/ │ └── ssaApi.ts # API 封装(含咨询 API) ├── types/ │ └── index.ts # 类型定义 └── styles/ └── ssa.css # 模块样式 ``` ### 1.1 🆕 双模式设计原则 | 原则 | 说明 | |------|------| | **模式切换** | 顶部 Tab 切换"智能分析"/"统计咨询" | | **无数据友好** | 咨询模式不要求上传数据 | | **SAP 导出** | 咨询完成后可下载 Word/Markdown | --- ## 2. 原型图核心元素解析 根据 `智能统计分析V2.html` 原型,需实现以下核心 UI: ### 2.1 整体布局(含模式切换) ``` ┌─────────────────────────────────────────────────────────────────┐ │ ┌───────────┐ ┌─────────────────────────────────────────────┐ │ │ │ │ │ 🆕 [智能分析] [统计咨询] ← 模式切换 Tab │ │ │ │ Sidebar │ │ Header (会话标题) │ │ │ │ │ ├─────────────────────────────────────────────┤ │ │ │ - 导入数据│ │ │ │ │ │ - 新会话 │ │ Chat Flow (消息流) │ │ │ │ - 历史 │ │ │ │ │ │ │ │ - SystemMessage (欢迎/上传引导) │ │ │ │ │ │ - UserMessage (用户输入) │ │ │ │ │ │ - PlanCard (计划确认) │ │ │ │ ─────── │ │ - ExecutionTrace (执行路径) │ │ │ │ 数据状态 │ │ - ResultCard (结果报告) │ │ │ │ (分析模式) │ │ - 🆕 SAPPreview (咨询模式) │ │ │ │ │ │ │ │ │ │ │ ├─────────────────────────────────────────────┤ │ │ │ │ │ InputArea (输入框 + 发送按钮) │ │ │ └───────────┘ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 核心组件设计规范 | 组件 | 原型特征 | 实现要点 | |------|---------|---------| | **SSASidebar** | 宽 256px,白色背景,阴影分隔 | Logo + 按钮 + 历史列表 + 数据状态 | | **PlanCard** | 圆角卡片,分组/检验变量展示,护栏警告 | 支持参数编辑、确认/修改按钮 | | **ExecutionTrace** | 竖向树状结构,带状态图标和连接线 | 动画展开,步骤状态(成功/警告/进行中) | | **ResultCard** | 多区块:三线表 + 图表 + 解读 + 下载 | APA 格式表格,Base64 图片渲染 | | **APATable** | 顶线(2px) + 表头下线(1px) + 底线(2px) | 数字右对齐,等宽字体 | --- ## 3. 核心组件实现 ### 3.1 PlanCard(计划确认卡片) ```tsx // components/cards/PlanCard.tsx import React from 'react'; import { Card, Button, Tag, Alert, Space, Descriptions } from 'antd'; import { PlayCircleOutlined, EditOutlined, SafetyOutlined } from '@ant-design/icons'; interface PlanCardProps { plan: { tool_code: string; tool_name: string; reasoning: string; params: Record; guardrails: { check_normality?: boolean; auto_fix?: boolean; }; }; dataSchema: { columns: Array<{ name: string; type: string; uniqueValues?: string[] }>; }; onConfirm: () => void; onEdit: () => void; loading?: boolean; } export const PlanCard: React.FC = ({ plan, dataSchema, onConfirm, onEdit, loading = false }) => { // 查找变量类型信息 const getColumnInfo = (colName: string) => { const col = dataSchema.columns.find(c => c.name === colName); if (!col) return ''; if (col.type === 'categorical' && col.uniqueValues) { return `(分类: ${col.uniqueValues.slice(0, 3).join('/')})`; } return `(${col.type === 'numeric' ? '数值型' : '分类型'})`; }; return ( 分析方案确认 {plan.tool_name} } styles={{ header: { background: '#f8fafc' } }} > {/* 变量映射 */}
{Object.entries(plan.params).map(([key, value]) => (
{key.replace(/_/g, ' ')}
{String(value)} {getColumnInfo(String(value))}
))}
{/* 护栏提示 */} {plan.guardrails.check_normality && ( } showIcon message="统计护栏 (自动执行)" description={
  • Shapiro-Wilk 正态性检验
  • Levene 方差齐性检验
  • {plan.guardrails.auto_fix && (
  • ⚠️ 若正态性检验失败,将自动降级为 Wilcoxon 秩和检验
  • )}
} className="mb-4" /> )} {/* 操作按钮 */}
); }; ``` ### 3.2 ExecutionTrace(执行路径树) ```tsx // components/cards/ExecutionTrace.tsx import React from 'react'; import { CheckCircleFilled, ExclamationCircleFilled, SwapOutlined, CalculatorOutlined, LoadingOutlined } from '@ant-design/icons'; interface TraceStep { id: string; label: string; status: 'success' | 'warning' | 'error' | 'running' | 'pending' | 'switched'; // 🆕 switched detail?: string; subLabel?: string; actionType?: 'Block' | 'Warn' | 'Switch'; // 🆕 护栏 Action 类型 switchTarget?: string; // 🆕 Switch 目标工具 } interface ExecutionTraceProps { steps: TraceStep[]; } export const ExecutionTrace: React.FC = ({ steps }) => { const getIcon = (status: TraceStep['status']) => { switch (status) { case 'success': return ; case 'warning': return ; case 'error': return ; case 'switched': // 🆕 方法切换 return ; case 'running': return ; default: return
; } }; // 🆕 获取 Action 类型标签 const getActionTag = (step: TraceStep) => { if (!step.actionType) return null; const tagStyles = { 'Block': 'bg-red-100 text-red-700 border-red-200', 'Warn': 'bg-amber-100 text-amber-700 border-amber-200', 'Switch': 'bg-blue-100 text-blue-700 border-blue-200' }; return ( {step.actionType} {step.switchTarget && → {step.switchTarget}} ); }; return (
执行路径 {steps.every(s => s.status === 'success') && ( 完成 )}
{/* 连接线 */}
{steps.map((step, idx) => (
{getIcon(step.status)}
{step.label} {step.detail && (
{step.detail} {step.subLabel && ( {step.subLabel} )}
)}
))}
); }; // 使用示例 const mockSteps: TraceStep[] = [ { id: '1', label: '加载数据 (n=150)', status: 'success' }, { id: '2', label: '正态性检验 (Shapiro-Wilk)', status: 'error', detail: 'P = 0.002 (< 0.05) ❌', subLabel: '-> 拒绝正态假设' }, { id: '3', label: '策略切换: T-Test -> Wilcoxon Test', status: 'warning' }, { id: '4', label: '计算完成', status: 'success' }, ]; ``` ### 3.3 ExecutionProgress(📌 执行进度动画) ```tsx // components/cards/ExecutionProgress.tsx import React, { useState, useEffect } from 'react'; import { LoadingOutlined, CheckCircleFilled } from '@ant-design/icons'; import { Progress, Typography } from 'antd'; const { Text } = Typography; interface ExecutionProgressProps { isExecuting: boolean; onComplete?: () => void; } // 📌 模拟进度文案(缓解用户等待焦虑) const PROGRESS_MESSAGES = [ '正在加载数据...', '执行统计护栏检验...', '进行核心计算...', '生成可视化图表...', '格式化结果...', '即将完成...' ]; export const ExecutionProgress: React.FC = ({ isExecuting, onComplete }) => { const [progress, setProgress] = useState(0); const [messageIndex, setMessageIndex] = useState(0); useEffect(() => { if (!isExecuting) { setProgress(0); setMessageIndex(0); return; } // 📌 模拟进度(实际进度由后端控制) const progressInterval = setInterval(() => { setProgress(prev => { if (prev >= 90) return prev; // 卡在 90%,等待真正完成 return prev + Math.random() * 10; }); }, 500); const messageInterval = setInterval(() => { setMessageIndex(prev => prev < PROGRESS_MESSAGES.length - 1 ? prev + 1 : prev ); }, 2000); return () => { clearInterval(progressInterval); clearInterval(messageInterval); }; }, [isExecuting]); if (!isExecuting) return null; return (
正在执行统计分析
{PROGRESS_MESSAGES[messageIndex]} 复杂计算可能需要 10-30 秒,请耐心等待...
); }; ``` ### 3.4 ResultCard(结果报告卡片) ```tsx // components/cards/ResultCard.tsx import React from 'react'; import { Button, Divider, Space, Typography } from 'antd'; import { DownloadOutlined, FileWordOutlined } from '@ant-design/icons'; import { APATable } from '../common/APATable'; import { PlotViewer } from '../common/PlotViewer'; const { Title, Paragraph, Text } = Typography; interface ResultCardProps { result: { method: string; statistic: number; p_value: number; p_value_fmt: string; // 🆕 R 服务返回的格式化 p 值 group_stats: Array<{ group: string; n: number; mean?: number; median?: number; sd?: number; iqr?: [number, number]; }>; }; plots: string[]; // Base64 图片 interpretation?: string; // Critic 解读 reproducibleCode: string; onDownloadCode: () => void; } export const ResultCard: React.FC = ({ result, plots, interpretation, reproducibleCode, onDownloadCode }) => { // 构造表格数据 const tableData = result.group_stats.map(g => ({ group: g.group, n: g.n, value: g.median ? `${g.median.toFixed(2)} [${g.iqr?.[0].toFixed(2)} - ${g.iqr?.[1].toFixed(2)}]` : `${g.mean?.toFixed(2)} ± ${g.sd?.toFixed(2)}` })); const columns = [ { key: 'group', title: 'Group', width: 120 }, { key: 'n', title: 'N', width: 60, align: 'right' as const }, { key: 'value', title: result.method.includes('Wilcoxon') ? 'Median [IQR]' : 'Mean ± SD' }, { key: 'statistic', title: 'Statistic', render: () => result.statistic.toFixed(2), rowSpan: tableData.length }, { key: 'pValue', title: 'P-Value', render: () => ( {/* 🆕 直接使用 R 服务返回的格式化值 */} {result.p_value_fmt} {result.p_value < 0.01 ? ' **' : result.p_value < 0.05 ? ' *' : ''} ), rowSpan: tableData.length }, ]; return (
{/* Header */}
分析结果报告 基于 {result.method}
{/* 1. 统计表格 */}
表 1. 组间差异比较 (三线表)
Note: {result.method.includes('Wilcoxon') ? 'IQR = Interquartile Range' : 'SD = Standard Deviation'}; * P < 0.05, ** P < 0.01.
{/* 2. 图表 */} {plots.length > 0 && (
图 1. 可视化结果
)} {/* 3. 方法与解读 */} {interpretation && (
方法与结果解读
)} {/* 4. 资产交付 */}
资产交付
); }; ``` ### 3.5 APATable(三线表) ```tsx // components/common/APATable.tsx import React from 'react'; import './APATable.css'; interface Column { key: string; title: string; width?: number; align?: 'left' | 'center' | 'right'; render?: (value: any, record: any, index: number) => React.ReactNode; rowSpan?: number; } interface APATableProps { columns: Column[]; data: Record[]; } export const APATable: React.FC = ({ columns, data }) => { return (
{columns.map(col => ( ))} {data.map((row, rowIdx) => ( {columns.map((col, colIdx) => { // 处理 rowSpan if (col.rowSpan && rowIdx > 0) return null; const value = col.render ? col.render(row[col.key], row, rowIdx) : row[col.key]; return ( ); })} ))}
{col.title}
{value}
); }; ``` ```css /* components/common/APATable.css */ .apa-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; font-size: 14px; } .apa-table thead th { border-top: 2px solid #1e293b; border-bottom: 1px solid #1e293b; padding: 8px 12px; text-align: left; font-weight: 600; color: #334155; } .apa-table tbody td { padding: 8px 12px; border-bottom: 1px solid #e2e8f0; color: #475569; } .apa-table tbody tr:last-child td { border-bottom: 2px solid #1e293b; } ``` --- ## 4. Zustand Store(含模式切换) ```typescript // store/ssaStore.ts import { create } from 'zustand'; // 🆕 模式类型 type SSAMode = 'analysis' | 'consult'; interface Message { id: string; role: 'user' | 'assistant' | 'system'; contentType: 'text' | 'plan' | 'result' | 'trace' | 'sap'; // 🆕 增加 sap 类型 content: any; createdAt: string; } // 🆕 SAP 文档类型 interface SAPDocument { title: string; sections: Array<{ heading: string; content: string }>; recommendedTools: string[]; } interface SSAState { // 🆕 模式 mode: SSAMode; // 会话 sessionId: string | null; sessionTitle: string; // 数据(分析模式) dataLoaded: boolean; dataSchema: object | null; dataFileName: string; dataRowCount: number; // 消息 messages: Message[]; // 执行状态 isPlanning: boolean; isExecuting: boolean; currentPlan: object | null; // 🆕 咨询模式状态 currentSAP: SAPDocument | null; isGeneratingSAP: boolean; // Actions setMode: (mode: SSAMode) => void; // 🆕 setSession: (id: string, title?: string) => void; setDataLoaded: (schema: object, fileName: string, rowCount: number) => void; addMessage: (message: Omit) => void; setPlanning: (planning: boolean) => void; setExecuting: (executing: boolean) => void; setCurrentPlan: (plan: object | null) => void; setCurrentSAP: (sap: SAPDocument | null) => void; // 🆕 setGeneratingSAP: (generating: boolean) => void; // 🆕 reset: () => void; } export const useSSAStore = create((set, get) => ({ mode: 'analysis', // 🆕 默认分析模式 sessionId: null, sessionTitle: '新会话', dataLoaded: false, dataSchema: null, dataFileName: '', dataRowCount: 0, messages: [], isPlanning: false, isExecuting: false, currentPlan: null, currentSAP: null, // 🆕 isGeneratingSAP: false, // 🆕 // 🆕 切换模式 setMode: (mode) => set({ mode, // 切换模式时重置会话 sessionId: null, messages: [], dataLoaded: false, currentPlan: null, currentSAP: null }), setSession: (id, title = '新会话') => set({ sessionId: id, sessionTitle: title }), setDataLoaded: (schema, fileName, rowCount) => set({ dataLoaded: true, dataSchema: schema, dataFileName: fileName, dataRowCount: rowCount }), addMessage: (message) => set(state => ({ messages: [ ...state.messages, { ...message, id: crypto.randomUUID(), createdAt: new Date().toISOString() } ] })), setPlanning: (planning) => set({ isPlanning: planning }), setExecuting: (executing) => set({ isExecuting: executing }), setCurrentPlan: (plan) => set({ currentPlan: plan }), setCurrentSAP: (sap) => set({ currentSAP: sap }), // 🆕 setGeneratingSAP: (generating) => set({ isGeneratingSAP: generating }), // 🆕 reset: () => set({ mode: 'analysis', sessionId: null, sessionTitle: '新会话', dataLoaded: false, dataSchema: null, dataFileName: '', dataRowCount: 0, messages: [], isPlanning: false, isExecuting: false, currentPlan: null, currentSAP: null, isGeneratingSAP: false }) })); ``` --- ## 5. API 封装(含咨询模式) ```typescript // api/ssaApi.ts import { apiClient } from '@/common/api/client'; const BASE = '/api/v1/ssa'; export const ssaApi = { // ==================== 智能分析模式 ==================== // 会话 createSession: () => apiClient.post<{ id: string }>(`${BASE}/sessions`), getSession: (id: string) => apiClient.get(`${BASE}/sessions/${id}`), listSessions: () => apiClient.get(`${BASE}/sessions`), // 数据上传 uploadData: (sessionId: string, file: File) => { const formData = new FormData(); formData.append('file', file); return apiClient.post(`${BASE}/sessions/${sessionId}/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); }, // 生成计划 generatePlan: (sessionId: string, query: string) => apiClient.post(`${BASE}/sessions/${sessionId}/plan`, { query }), // 执行分析(📌 超时 120s) executeAnalysis: (sessionId: string, plan: object) => apiClient.post(`${BASE}/sessions/${sessionId}/execute`, { plan }, { timeout: 120000 // 📌 120s 超时,应对复杂计算 }), // 下载代码 downloadCode: (sessionId: string, messageId: string) => apiClient.get(`${BASE}/sessions/${sessionId}/download-code/${messageId}`, { responseType: 'blob' }), // ==================== 🆕 咨询模式 ==================== // 创建咨询会话(无数据) createConsultSession: () => apiClient.post<{ id: string }>(`${BASE}/consult`), // 咨询对话 consultChat: (sessionId: string, message: string) => apiClient.post<{ response: string }>(`${BASE}/consult/${sessionId}/chat`, { message }), // 生成 SAP 文档 generateSAP: (sessionId: string) => apiClient.post<{ title: string; sections: Array<{ heading: string; content: string }>; recommendedTools: string[]; }>(`${BASE}/consult/${sessionId}/generate-sap`), // 下载 SAP(Word/Markdown) downloadSAP: (sessionId: string, format: 'word' | 'markdown' = 'word') => apiClient.get(`${BASE}/consult/${sessionId}/download-sap`, { params: { format }, responseType: 'blob' }), // ==================== 🆕 配置中台 ==================== // 导入配置 importConfig: (file: File) => { const formData = new FormData(); formData.append('file', file); return apiClient.post(`${BASE}/config/import`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); }, // 获取工具列表 getConfigTools: () => apiClient.get(`${BASE}/config/tools`), // 热加载配置(Admin) reloadConfig: () => apiClient.post(`${BASE}/config/reload`), }; ``` --- ## 6. 模块注册 ```typescript // index.ts import { lazy } from 'react'; const SSAWorkspace = lazy(() => import('./pages/SSAWorkspace')); export const ssaRoutes = [ { path: '/ssa', element: , meta: { title: '智能统计分析', icon: 'BarChartOutlined', requireAuth: true } } ]; // 在 moduleRegistry.ts 中注册 // import { ssaRoutes } from './modules/ssa'; // registerModule('ssa', ssaRoutes); ``` --- ## 7. 样式规范 ### 7.1 颜色系统(与原型对齐) ```css /* styles/ssa.css */ :root { --ssa-primary: #3b82f6; /* blue-500 */ --ssa-primary-hover: #2563eb; /* blue-600 */ --ssa-bg: #f8fafc; /* slate-50 */ --ssa-card-bg: #ffffff; --ssa-border: #e2e8f0; /* slate-200 */ --ssa-text: #334155; /* slate-700 */ --ssa-text-muted: #94a3b8; /* slate-400 */ --ssa-success: #22c55e; /* green-500 */ --ssa-warning: #f59e0b; /* amber-500 */ --ssa-error: #ef4444; /* red-500 */ } ``` ### 7.2 动画 ```css /* 渐入动画 */ .ssa-fade-in { animation: ssaFadeIn 0.4s ease-out forwards; opacity: 0; } /* 上滑动画 */ .ssa-slide-up { animation: ssaSlideUp 0.4s ease-out forwards; opacity: 0; transform: translateY(10px); } @keyframes ssaFadeIn { to { opacity: 1; } } @keyframes ssaSlideUp { to { transform: translateY(0); opacity: 1; } } ``` --- ## 8. 开发检查清单 | 组件 | 功能 | 状态 | |------|------|------| | SSASidebar | 导入数据、新建会话、历史列表、数据状态 | ⬜ | | 🆕 **ModeSwitch** | **模式切换 Tab(智能分析/统计咨询)** | ⬜ | | DataUploader | 拖拽/点击上传,进度显示 | ⬜ | | MessageList | 消息流滚动,自动滚底 | ⬜ | | PlanCard | 参数展示、护栏提示、确认/修改按钮 | ⬜ | | 🆕 PlanCard | **增加"仅下载方案"按钮(咨询模式)** | ⬜ | | ExecutionTrace | 步骤树、状态图标、连接线 | ⬜ | | **ExecutionProgress** | **📌 执行中进度动画,缓解等待焦虑** | ⬜ | | ResultCard | 三线表、图表、解读、下载按钮 | ⬜ | | APATable | APA 格式表格样式 | ⬜ | | 🆕 **ConsultChat** | **无数据咨询对话界面** | ⬜ | | 🆕 **SAPPreview** | **SAP 文档预览/下载** | ⬜ | | 🆕 **SAPDownloadButton** | **Word/Markdown 下载选择** | ⬜ | | Zustand Store | 状态管理,**含 mode 切换** | ⬜ | | API 对接 | 所有接口联调,**含咨询 API** | ⬜ | --- ## 9. 🆕 新增组件实现 ### 9.1 ModeSwitch(模式切换 Tab) ```tsx // components/layout/ModeSwitch.tsx import React from 'react'; import { Segmented } from 'antd'; import { BarChartOutlined, MessageOutlined } from '@ant-design/icons'; import { useSSAStore } from '../../store/ssaStore'; export const ModeSwitch: React.FC = () => { const { mode, setMode } = useSSAStore(); return ( setMode(value as 'analysis' | 'consult')} options={[ { label: (
智能分析
), value: 'analysis', }, { label: (
统计咨询
), value: 'consult', }, ]} className="bg-slate-100" /> ); }; ``` ### 9.2 ConsultChat(无数据咨询界面) ```tsx // components/consult/ConsultChat.tsx import React, { useState } from 'react'; import { Input, Button, Alert } from 'antd'; import { SendOutlined, FileWordOutlined } from '@ant-design/icons'; import { useSSAStore } from '../../store/ssaStore'; import { ssaApi } from '../../api/ssaApi'; export const ConsultChat: React.FC = () => { const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const { sessionId, messages, addMessage, setSession, setCurrentSAP, setGeneratingSAP, isGeneratingSAP } = useSSAStore(); const handleSend = async () => { if (!input.trim()) return; setLoading(true); // 如果没有会话,先创建 let currentSessionId = sessionId; if (!currentSessionId) { const { data } = await ssaApi.createConsultSession(); currentSessionId = data.id; setSession(data.id, '统计咨询'); } // 添加用户消息 addMessage({ role: 'user', contentType: 'text', content: { text: input } }); setInput(''); // 发送咨询 const { data } = await ssaApi.consultChat(currentSessionId!, input); // 添加 AI 回复 addMessage({ role: 'assistant', contentType: 'text', content: { text: data.response } }); setLoading(false); }; const handleGenerateSAP = async () => { if (!sessionId) return; setGeneratingSAP(true); const { data } = await ssaApi.generateSAP(sessionId); setCurrentSAP(data); addMessage({ role: 'assistant', contentType: 'sap', content: data }); setGeneratingSAP(false); }; return (
{/* 引导提示 */} {/* 消息流 */}
{messages.map(msg => (
{msg.contentType === 'sap' ? : msg.content.text }
))}
{/* 输入区 */}
setInput(e.target.value)} placeholder="描述您的研究设计和统计分析需求..." autoSize={{ minRows: 2, maxRows: 4 }} onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); handleSend(); } }} />
); }; ``` ### 9.3 SAPPreview(SAP 文档预览) ```tsx // components/cards/SAPPreview.tsx import React from 'react'; import { Card, Button, Space, Typography, Divider, Tag } from 'antd'; import { DownloadOutlined, FileWordOutlined, FileMarkdownOutlined } from '@ant-design/icons'; import { ssaApi } from '../../api/ssaApi'; import { useSSAStore } from '../../store/ssaStore'; const { Title, Paragraph, Text } = Typography; interface SAPDocument { title: string; sections: Array<{ heading: string; content: string }>; recommendedTools: string[]; } interface SAPPreviewProps { sap: SAPDocument; } export const SAPPreview: React.FC = ({ sap }) => { const { sessionId } = useSSAStore(); const handleDownload = async (format: 'word' | 'markdown') => { if (!sessionId) return; const response = await ssaApi.downloadSAP(sessionId, format); const blob = new Blob([response.data]); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = format === 'word' ? 'SAP.docx' : 'SAP.md'; a.click(); window.URL.revokeObjectURL(url); }; return ( 统计分析计划 (SAP) } extra={ } > {sap.title} {sap.sections.map((section, idx) => (
{section.heading} {section.content}
))}
推荐统计方法:
{sap.recommendedTools.map((tool, idx) => ( {tool} ))}
); }; ``` ### 9.4 PlanCard 增强(支持仅下载方案) ```tsx // components/cards/PlanCard.tsx 增加的按钮 // 在 "确认并执行" 按钮旁边添加: {/* 🆕 仅下载方案(咨询模式下或用户选择不执行) */} ``` --- ## 9. 关键配置 ### 9.1 Axios 全局超时配置 ```typescript // api/client.ts import axios from 'axios'; export const apiClient = axios.create({ baseURL: '/api', timeout: 60000, // 默认 60s headers: { 'Content-Type': 'application/json' } }); // 📌 对于 SSA 执行接口,单独设置 120s 超时 // 见 ssaApi.executeAnalysis ```