實作完整分享、刪除、下載報告功能
This commit is contained in:
236
app/api/evaluation/[id]/download/route.ts
Normal file
236
app/api/evaluation/[id]/download/route.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { EvaluationService } from '@/lib/services/database';
|
||||
import { generateHTMLPDFReport, type PDFReportData } from '@/lib/utils/html-pdf-generator';
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const evaluationId = parseInt(params.id);
|
||||
|
||||
if (isNaN(evaluationId)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '無效的評審ID' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`📄 開始生成 PDF 報告: ID=${evaluationId}`);
|
||||
|
||||
// 獲取評審詳細數據
|
||||
const evaluationWithDetails = await EvaluationService.findWithDetails(evaluationId);
|
||||
|
||||
if (!evaluationWithDetails) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '找不到指定的評審記錄' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`✅ 成功獲取評審數據: 專案=${evaluationWithDetails.project?.title}`);
|
||||
|
||||
// 處理反饋數據,按類型分組
|
||||
const feedbackByType = {
|
||||
criteria: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'criteria') || [],
|
||||
strength: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'strength') || [],
|
||||
improvement: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'improvement') || [],
|
||||
overall: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'overall') || []
|
||||
};
|
||||
|
||||
// 為每個評分標準獲取對應的 AI 評語、優點和改進建議
|
||||
const criteriaWithFeedback = evaluationWithDetails.scores?.map(score => {
|
||||
const criteriaId = score.criteria_item_id;
|
||||
const criteriaName = score.criteria_item_name || '未知項目';
|
||||
|
||||
// 獲取該評分標準的 AI 評語
|
||||
const criteriaFeedback = feedbackByType.criteria
|
||||
.filter(f => f.criteria_item_id === criteriaId)
|
||||
.map(f => f.content)
|
||||
.filter((content, index, arr) => arr.indexOf(content) === index)[0] || '無評語';
|
||||
|
||||
// 獲取該評分標準的優點
|
||||
const strengths = feedbackByType.strength
|
||||
.filter(f => f.criteria_item_id === criteriaId)
|
||||
.map(f => f.content)
|
||||
.filter((content, index, arr) => arr.indexOf(content) === index);
|
||||
|
||||
// 獲取該評分標準的改進建議
|
||||
const improvements = feedbackByType.improvement
|
||||
.filter(f => f.criteria_item_id === criteriaId)
|
||||
.map(f => f.content)
|
||||
.filter((content, index, arr) => arr.indexOf(content) === index);
|
||||
|
||||
return {
|
||||
name: criteriaName,
|
||||
score: Number(score.score) || 0,
|
||||
maxScore: Number(score.max_score) || 10,
|
||||
weight: Number(score.weight) || 1,
|
||||
weightedScore: Number(score.weighted_score) || 0,
|
||||
percentage: Number(score.percentage) || 0,
|
||||
feedback: criteriaFeedback,
|
||||
strengths: strengths,
|
||||
improvements: improvements
|
||||
};
|
||||
}) || [];
|
||||
|
||||
// 構建 PDF 報告數據
|
||||
const pdfData: PDFReportData = {
|
||||
projectTitle: evaluationWithDetails.project?.title || '未知專案',
|
||||
overallScore: Number(evaluationWithDetails.overall_score) || 0,
|
||||
totalPossible: Number(evaluationWithDetails.max_possible_score) || 100,
|
||||
grade: evaluationWithDetails.grade || 'N/A',
|
||||
analysisDate: evaluationWithDetails.created_at ? new Date(evaluationWithDetails.created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
|
||||
criteria: criteriaWithFeedback,
|
||||
overview: {
|
||||
excellentItems: evaluationWithDetails.excellent_items || 0,
|
||||
improvementItems: evaluationWithDetails.improvement_items || 0,
|
||||
overallPerformance: Number(evaluationWithDetails.overall_score) || 0
|
||||
},
|
||||
improvementSuggestions: {
|
||||
overallSuggestions: feedbackByType.overall[0]?.content || '無詳細分析',
|
||||
maintainStrengths: feedbackByType.strength
|
||||
.filter(f => f.criteria_item_id === null)
|
||||
.map(f => {
|
||||
const content = f.content || '無描述';
|
||||
const colonIndex = content.indexOf(':');
|
||||
const title = colonIndex > -1 ? content.substring(0, colonIndex + 1) : '系統優勢:';
|
||||
const description = colonIndex > -1 ? content.substring(colonIndex + 1).trim() : content;
|
||||
|
||||
return {
|
||||
title: title,
|
||||
description: description
|
||||
};
|
||||
}),
|
||||
keyImprovements: (() => {
|
||||
const allImprovementData = feedbackByType.improvement
|
||||
.filter(f => f.criteria_item_id === null);
|
||||
|
||||
const improvementData = allImprovementData.length >= 5
|
||||
? allImprovementData.slice(1, -3)
|
||||
: allImprovementData;
|
||||
|
||||
const tempGroups = [];
|
||||
let currentGroup = null;
|
||||
|
||||
for (const item of improvementData) {
|
||||
const content = item.content || '';
|
||||
const colonIndex = content.indexOf(':');
|
||||
|
||||
if (colonIndex > -1) {
|
||||
const title = content.substring(0, colonIndex + 1);
|
||||
const remainingContent = content.substring(colonIndex + 1).trim();
|
||||
|
||||
const suggestionIndex = remainingContent.indexOf('建議:');
|
||||
|
||||
if (suggestionIndex > -1) {
|
||||
if (currentGroup) {
|
||||
tempGroups.push(currentGroup);
|
||||
}
|
||||
|
||||
currentGroup = {
|
||||
title: title,
|
||||
subtitle: '建議:',
|
||||
suggestions: []
|
||||
};
|
||||
|
||||
const suggestionsText = remainingContent.substring(suggestionIndex + 3).trim();
|
||||
const suggestions = suggestionsText.split('\n')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.startsWith('•'))
|
||||
.map(s => s.substring(1).trim());
|
||||
|
||||
currentGroup.suggestions = suggestions;
|
||||
} else if (remainingContent.startsWith('•')) {
|
||||
if (currentGroup) {
|
||||
const suggestion = remainingContent.substring(1).trim();
|
||||
currentGroup.suggestions.push(suggestion);
|
||||
}
|
||||
} else {
|
||||
if (currentGroup) {
|
||||
tempGroups.push(currentGroup);
|
||||
}
|
||||
|
||||
currentGroup = {
|
||||
title: title,
|
||||
subtitle: '',
|
||||
suggestions: [remainingContent]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (currentGroup) {
|
||||
tempGroups.push(currentGroup);
|
||||
}
|
||||
|
||||
if (content.startsWith('•')) {
|
||||
if (currentGroup) {
|
||||
const suggestion = content.substring(1).trim();
|
||||
currentGroup.suggestions.push(suggestion);
|
||||
} else {
|
||||
currentGroup = {
|
||||
title: '改進方向:',
|
||||
subtitle: '',
|
||||
suggestions: [content.substring(1).trim()]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
currentGroup = {
|
||||
title: '改進方向:',
|
||||
subtitle: '',
|
||||
description: content,
|
||||
suggestions: []
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentGroup) {
|
||||
tempGroups.push(currentGroup);
|
||||
}
|
||||
|
||||
return tempGroups;
|
||||
})(),
|
||||
actionPlan: feedbackByType.improvement
|
||||
.filter(f => f.criteria_item_id === null)
|
||||
.slice(-3)
|
||||
.map((f, index) => {
|
||||
const content = f.content || '無描述';
|
||||
const colonIndex = content.indexOf(':');
|
||||
const phase = colonIndex > -1 ? content.substring(0, colonIndex + 1) : `階段 ${index + 1}:`;
|
||||
const description = colonIndex > -1 ? content.substring(colonIndex + 1).trim() : content;
|
||||
|
||||
return {
|
||||
phase: phase,
|
||||
description: description
|
||||
};
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`📋 開始生成 PDF 報告: 專案=${pdfData.projectTitle}`);
|
||||
|
||||
// 生成 PDF
|
||||
const pdfBlob = await generateHTMLPDFReport(pdfData);
|
||||
|
||||
console.log(`✅ PDF 報告生成完成: 大小=${pdfBlob.size} bytes`);
|
||||
|
||||
// 返回 PDF 文件
|
||||
const fileName = `評審報告_${pdfData.projectTitle}_${pdfData.analysisDate}.pdf`;
|
||||
|
||||
return new NextResponse(pdfBlob, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment; filename="${encodeURIComponent(fileName)}"`,
|
||||
'Content-Length': pdfBlob.size.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 生成 PDF 報告失敗:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '生成 PDF 報告失敗' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user