# 拍照解题功能 - 前端设计文档 **文档版本**: v1.0 **创建日期**: 2025-01-29 **设计者**: Frontend Team **状态**: 设计阶段 --- ## 一、设计原则 ### 1.1 复用现有组件 - **ToolboxAppLayout**: 统一的工具箱应用布局 - **ImageUploadZone**: 图片上传组件(来自 OCR 功能) - **MarkdownRenderer**: Markdown 渲染组件(支持数学公式) - **Toast**: 全局提示组件 - 参考 QwenOCR 和 ImageTranslation 的交互模式 ### 1.2 保持风格一致 - 使用 Tailwind CSS 实用类 - 遵循现有的颜色方案(蓝色主题) - 保持圆角、阴影、过渡动画的一致性 - 使用 Lucide React 图标库 --- ## 二、页面结构设计 ### 2.1 路由配置 **路径**: `/toolbox/photo-answer` **页面组件**: `frontend/pages/PhotoAnswer.tsx` ### 2.2 整体布局 ``` ┌─────────────────────────────────────────────────────────────┐ │ ToolboxAppLayout │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ Header: 拍照解题 + 返回按钮 │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────┬─────────────────┬─────────────────┐ │ │ │ 图片上传区 │ 参数设置区 │ │ │ │ (2列) │ │ │ │ │ 开始按钮 │ (1列) │ │ └─────────────────┴─────────────────┴─────────────────┘ │ │ │ │ ┌─────────────────┬─────────────────────────────────────┐ │ │ │ 历史记录列表 │ 解答结果展示区 │ │ │ │ (1列) │ (2列) │ │ │ └─────────────────┴─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 三、核心组件设计 ### 3.1 主页面组件 (PhotoAnswer.tsx) **状态管理**: ```typescript // 图片上传 const [uploadedImages, setUploadedImages] = useState([]); const [viewingImage, setViewingImage] = useState(null); // 参数设置 const [grade, setGrade] = useState(0); const [stage, setStage] = useState('other'); const [subject, setSubject] = useState('other'); // 任务状态 const [isProcessing, setIsProcessing] = useState(false); const [error, setError] = useState(null); // 解答结果 const [currentAnswer, setCurrentAnswer] = useState(''); const [answerChunks, setAnswerChunks] = useState([]); // 历史记录 const [history, setHistory] = useState([]); const [selectedHistoryId, setSelectedHistoryId] = useState(null); ``` **复用组件**: - `ImageUploadZone`: 图片上传(maxImages=1,单题模式) - `ToolboxAppLayout`: 页面布局 - `MarkdownRenderer`: 解答内容渲染(支持 LaTeX 公式) --- ### 3.2 参数设置组件 (AnswerParamsSelector.tsx) **功能**: 选择年级、学段、学科 **UI 设计**: ```tsx

解题参数

{/* 年级选择 */}
{/* 学段选择 */}
{/* 学科选择 */}
``` **样式特点**: - 按钮式选择器,选中状态:`bg-blue-50 border-blue-500 text-blue-700` - 未选中状态:`bg-gray-50 border-gray-200 text-gray-700 hover:border-blue-300` --- ### 3.3 解答结果组件 (AnswerDisplay.tsx) **功能**: 流式展示解题过程,支持 Markdown 和 LaTeX **UI 设计**: ```tsx

解题过程

{/* 流式输出区域 */}
{isProcessing && !currentAnswer && (
AI 正在思考...
)} {currentAnswer && ( )} {!currentAnswer && !isProcessing && (
上传题目图片并点击"开始解题"
)}
``` **流式渲染优化**: - 使用 `MarkdownRenderer` 的 `isStreaming` 属性 - 流式输出时简化渲染,完成后才做完整 Markdown 解析 - 自动滚动到底部(参考 TextInteraction 的实现) --- ### 3.4 历史记录组件 (AnswerHistory.tsx) **功能**: 展示解题历史,支持查看、下载、删除 **UI 设计**: ```tsx

解题历史 ({history.length})

{history.map((item) => (
handleSelectHistory(item)} className={`p-3 rounded-lg border cursor-pointer transition-all ${ selectedHistoryId === item.id ? 'border-blue-500 bg-blue-50' : 'border-gray-100 hover:border-blue-200 hover:bg-gray-50' }`} >
{item.question_preview}
{item.subject} · {formatTime(item.created_at)}
{/* 题目缩略图 */} {item.thumbnail && ( 题目缩略图 )}
))}
``` --- ## 四、交互流程设计 ### 4.1 单题解答流程 1. **上传题目图片** - 用户点击或拖拽上传图片 - 显示图片预览(支持全屏查看) - 图片上传至 OSS,获取公网 URL 2. **设置解题参数**(可选) - 选择年级、学段、学科 - 参数会影响解题的针对性 3. **开始解题** - 点击"开始解题"按钮 - 按钮状态变为 ` 解题中...` - 调用后端 SSE 流式接口 4. **流式展示解答** - 实时接收解答内容 - 使用 `MarkdownRenderer` 渲染 - 支持数学公式(LaTeX) - 自动滚动到底部 5. **解答完成** - 显示完整解答内容 - 保存到历史记录 - 支持复制、下载 ### 4.2 试卷切题流程(可选功能) 1. **上传试卷图片** - 支持多页试卷上传 - 显示上传进度 2. **自动切分题目** - 调用切题 API - 显示切分结果(题目列表) 3. **选择题目解答** - 点击题目卡片 - 自动填充到解答区域 - 开始解题流程 --- ## 五、API 服务层设计 ### 5.1 API 服务文件 (frontend/services/photoAnswerApi.ts) ```typescript import axios from 'axios'; import { authService } from './authService'; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; export interface AnswerRequest { image_url: string; parameters?: { grade?: number; stage?: string; subject?: string; }; } export interface AnswerHistoryItem { id: string; image_url: string; question_preview: string; answer_content: string; grade: number; subject: string; created_at: string; thumbnail?: string; } class PhotoAnswerApi { // SSE 流式解答 async answerStream( request: AnswerRequest, onChunk: (chunk: string) => void, onError: (error: Error) => void, onComplete: () => void ): Promise { const eventSource = new EventSource( `${API_BASE_URL}/api/edu/answer?` + new URLSearchParams({ image_url: request.image_url, grade: String(request.parameters?.grade || 0), stage: request.parameters?.stage || 'other', subject: request.parameters?.subject || 'other', }) ); eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'chunk') { onChunk(data.content); } else if (data.type === 'finish') { onComplete(); eventSource.close(); } } catch (err) { onError(err as Error); } }; eventSource.onerror = (err) => { onError(new Error('SSE 连接错误')); eventSource.close(); }; } // 获取历史记录 async getHistory(page: number = 1, pageSize: number = 20) { const response = await axios.get(`${API_BASE_URL}/api/edu/answer/history`, { params: { page, page_size: pageSize }, headers: { Authorization: authService.getAuthHeader() || '' }, }); return response.data; } // 删除历史记录 async deleteHistory(id: string) { const response = await axios.delete( `${API_BASE_URL}/api/edu/answer/history/${id}`, { headers: { Authorization: authService.getAuthHeader() || '' } } ); return response.data; } } export const photoAnswerApi = new PhotoAnswerApi(); ``` --- ## 六、样式规范 ### 6.1 颜色方案 | 用途 | 颜色类 | 说明 | |------|--------|------| | 主色调 | `bg-blue-600` | 按钮、强调元素 | | 选中状态 | `bg-blue-50 border-blue-500 text-blue-700` | 参数选择器 | | 背景 | `bg-gray-50` | 卡片背景 | | 边框 | `border-gray-100` | 默认边框 | | 文字 | `text-gray-700` | 主要文字 | | 次要文字 | `text-gray-400` | 辅助信息 | | 错误 | `bg-red-50 border-red-200 text-red-700` | 错误提示 | ### 6.2 圆角和阴影 - 卡片圆角:`rounded-2xl` - 按钮圆角:`rounded-lg` - 输入框圆角:`rounded-lg` - 卡片阴影:`shadow-sm` - 按钮阴影:`shadow-lg shadow-blue-500/20` ### 6.3 间距规范 - 卡片内边距:`p-4` - 元素间距:`space-y-4` 或 `space-x-2` - 栅格间距:`gap-4` --- ## 七、响应式设计 ### 7.1 断点设置 - 移动端:`< 768px` - 平板:`768px - 1024px` - 桌面:`> 1024px` ### 7.2 布局适配 **桌面端** (lg:): ```tsx
图片上传
参数设置
操作按钮
历史记录
解答结果
``` **移动端**: - 所有区域垂直堆叠 - 历史记录折叠为抽屉式 - 图片预览全屏显示 --- ## 八、性能优化 ### 8.1 图片优化 - 上传前压缩图片(< 2MB) - 使用 WebP 格式(如果浏览器支持) - 缩略图懒加载 ### 8.2 渲染优化 - 使用 `React.memo` 包裹 `MarkdownRenderer` - 流式输出时简化 Markdown 渲染 - 虚拟滚动历史记录列表(如果数量 > 50) ### 8.3 网络优化 - SSE 连接超时处理(100s) - 失败自动重试(最多 3 次) - 请求取消机制(用户中断) --- ## 九、错误处理 ### 9.1 错误类型 | 错误场景 | 提示信息 | 处理方式 | |----------|----------|----------| | 图片上传失败 | "图片上传失败,请重试" | 显示错误提示,允许重新上传 | | 图片格式不支持 | "仅支持 JPG、PNG 格式" | 阻止上传,提示用户 | | 图片过大 | "图片大小不能超过 10MB" | 阻止上传,提示压缩 | | API 调用失败 | "解题失败,请稍后重试" | 显示错误提示,保留输入 | | SSE 连接中断 | "连接中断,正在重试..." | 自动重连(最多 3 次)| | 识别失败 | "题目识别失败,请上传更清晰的图片" | 提示用户重新上传 | ### 9.2 错误提示组件 ```tsx {error && (
{error}
)} ``` --- ## 十、可访问性 (A11y) ### 10.1 键盘导航 - 所有交互元素支持 Tab 键导航 - 按钮支持 Enter/Space 触发 - 图片预览支持 Esc 关闭 ### 10.2 屏幕阅读器 - 图片添加 `alt` 属性 - 按钮添加 `aria-label` - 状态变化使用 `aria-live` ### 10.3 对比度 - 文字与背景对比度 ≥ 4.5:1 - 按钮禁用状态明显(`opacity-50`) --- ## 十一、开发计划 ### 11.1 组件开发顺序 | 阶段 | 组件 | 预计时间 | |------|------|----------| | Phase 1 | PhotoAnswer 主页面 + ImageUploadZone 集成 | 0.5 天 | | Phase 2 | AnswerParamsSelector 参数选择器 | 0.5 天 | | Phase 3 | AnswerDisplay 解答展示 + SSE 流式 | 1 天 | | Phase 4 | AnswerHistory 历史记录 | 0.5 天 | | Phase 5 | photoAnswerApi 服务层 | 0.5 天 | | Phase 6 | 响应式适配 + 错误处理 | 0.5 天 | | Phase 7 | 测试 + 优化 | 0.5 天 | **总计**: 4 天 ### 11.2 依赖检查 **已有依赖**: - `react-markdown`: Markdown 渲染 ✅ - `katex`: 数学公式渲染 ✅ - `lucide-react`: 图标库 ✅ - `axios`: HTTP 请求 ✅ **无需新增依赖** ✅ --- ## 十二、测试策略 ### 12.1 单元测试 - 参数选择器状态管理 - 历史记录列表渲染 - 错误处理逻辑 ### 12.2 集成测试 - 完整解题流程 - SSE 流式接收 - 图片上传 + OSS ### 12.3 用户测试 - 不同设备适配(手机、平板、桌面) - 不同题型识别准确率 - 流式输出流畅度 --- ## 十三、后续优化方向 ### 13.1 功能增强 - 支持手写题目拍照识别 - 支持拍照后本地裁剪 - 支持多题批量解答 - 支持语音播报解答过程 ### 13.2 交互优化 - 解答过程可暂停/继续 - 支持追问(多轮对话) - 支持收藏优质解答 - 支持分享解答链接 ### 13.3 智能化 - 根据历史推荐相似题目 - 智能识别薄弱知识点 - 生成个性化练习题 --- ## 附录 ### A. 文件结构 ``` frontend/ ├── pages/ │ └── PhotoAnswer.tsx # 主页面 ├── components/ │ └── photo-answer/ │ ├── AnswerParamsSelector.tsx # 参数选择器 │ ├── AnswerDisplay.tsx # 解答展示 │ └── AnswerHistory.tsx # 历史记录 ├── services/ │ └── photoAnswerApi.ts # API 服务 └── types/ └── photoAnswer.ts # TypeScript 类型定义 ``` ### B. 类型定义 (frontend/types/photoAnswer.ts) ```typescript export interface AnswerRequest { image_url: string; parameters?: { grade?: number; stage?: string; subject?: string; }; } export interface AnswerHistoryItem { id: string; image_url: string; question_preview: string; answer_content: string; grade: number; subject: string; created_at: string; thumbnail?: string; input_tokens: number; output_tokens: number; } export interface AnswerChunk { type: 'chunk' | 'finish'; content?: string; finish_reason?: string; tokens?: { input: number; output: number; }; } ``` ### C. 参考页面 - **QwenOCR.tsx**: 图片上传 + 历史记录布局 - **ImageTranslation.tsx**: 参数选择 + 结果展示 - **TextInteraction.tsx**: 流式输出 + 自动滚动 --- **文档结束**