feat: add ai recommendation exports

This commit is contained in:
Codex
2026-06-30 19:04:30 +08:00
parent ba737d41f5
commit 686b3609cf
13 changed files with 380 additions and 12 deletions

View File

@@ -1,6 +1,8 @@
import { useEffect, useState } from 'react';
import Taro from '@tarojs/taro';
import { Button, Input, Picker, Text, Textarea, View } from '@tarojs/components';
import {
exportSchoolRecommendationReport,
generateSchoolRecommendation,
loadSchoolRecommendationReports,
type SchoolRecommendationReport,
@@ -22,6 +24,24 @@ function recommendationRows(report: SchoolRecommendationReport | null) {
return report?.resultPayload?.recommendedSchools || [];
}
function saveExportFile(fileName: string, content: string, mimeType: string) {
if (process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' && typeof document !== 'undefined') {
const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.rel = 'noopener noreferrer';
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
return;
}
Taro.setClipboardData({ data: content });
Taro.showToast({ title: '报告内容已复制', icon: 'none' });
}
export default function StudentAiSchoolPage() {
const [profile, setProfile] = useState<StudentProfile | null>(null);
const [reports, setReports] = useState<SchoolRecommendationReport[]>([]);
@@ -30,6 +50,7 @@ export default function StudentAiSchoolPage() {
const [constraints, setConstraints] = useState('');
const [riskIndex, setRiskIndex] = useState(0);
const [loading, setLoading] = useState(false);
const [exporting, setExporting] = useState('');
const [error, setError] = useState('');
useEffect(() => {
@@ -65,6 +86,22 @@ export default function StudentAiSchoolPage() {
}
}
async function handleExport(format: 'markdown' | 'html') {
if (!current?.id) return;
setExporting(format);
setError('');
try {
const payload = await exportSchoolRecommendationReport(current.id, format);
if (!payload.item?.contentText) throw new Error('后端未返回报告内容');
saveExportFile(payload.item.fileName, payload.item.contentText, payload.item.mimeType);
if (process.env.TARO_ENV === 'h5') Taro.showToast({ title: '报告已导出', icon: 'success' });
} catch (nextError) {
setError(nextError instanceof Error ? nextError.message : '导出失败');
} finally {
setExporting('');
}
}
const rows = recommendationRows(current);
return (
@@ -123,6 +160,10 @@ export default function StudentAiSchoolPage() {
<Text className='hero-title'>{riskLabel(current.resultPayload.riskLevel)}</Text>
<Text className='hero-copy'>{current.resultPayload.summary}</Text>
</View>
<View className='toolbar wrap compact-toolbar'>
<Button className='secondary-button' loading={exporting === 'markdown'} onClick={() => handleExport('markdown')}>MD</Button>
<Button className='secondary-button' loading={exporting === 'html'} onClick={() => handleExport('html')}>HTML</Button>
</View>
<View className='list-stack'>
{rows.map(item => (
<View className='list-row' key={`${item.schoolId || item.schoolName}:${item.majorId || item.majorName || ''}`}>

View File

@@ -63,6 +63,17 @@ export interface SchoolRecommendationReport {
updatedAt: string;
}
export interface SchoolRecommendationExport {
reportId: string;
format: 'markdown' | 'html';
fileName: string;
mimeType: string;
contentBase64: string;
contentText?: string;
sha256: string;
sizeBytes: number;
}
export async function generateSchoolRecommendation(input: GenerateSchoolRecommendationInput) {
return apiRequest<{ item?: SchoolRecommendationReport }>('/api/ai/school-recommendations/generate', {
method: 'POST',
@@ -81,3 +92,9 @@ export async function loadSchoolRecommendationReport(reportId: string) {
query: { reportId },
});
}
export async function exportSchoolRecommendationReport(reportId: string, format: 'markdown' | 'html' = 'markdown') {
return apiRequest<{ item?: SchoolRecommendationExport }>('/api/ai/school-recommendations/export', {
query: { reportId, format },
});
}