實作所有測驗結果與資料庫整合
This commit is contained in:
221
app/api/admin/test-results/export/route.ts
Normal file
221
app/api/admin/test-results/export/route.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { getAllTestResults } from "@/lib/database/models/test_result"
|
||||
import { getLogicTestAnswersByTestResultId } from "@/lib/database/models/logic_test_answer"
|
||||
import { getCreativeTestAnswersByTestResultId } from "@/lib/database/models/creative_test_answer"
|
||||
import { getAllCombinedTestResults } from "@/lib/database/models/combined_test_result"
|
||||
import { getAllUsers } from "@/lib/database/models/user"
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const search = searchParams.get("search") || ""
|
||||
const department = searchParams.get("department") || "all"
|
||||
const testType = searchParams.get("testType") || "all"
|
||||
|
||||
// 獲取所有用戶
|
||||
const users = await getAllUsers()
|
||||
const userMap = new Map(users.map(user => [user.id, user]))
|
||||
|
||||
// 獲取所有測試結果
|
||||
const [testResults, combinedResults] = await Promise.all([
|
||||
getAllTestResults(),
|
||||
getAllCombinedTestResults()
|
||||
])
|
||||
|
||||
// 合併所有測試結果
|
||||
const allResults = []
|
||||
|
||||
// 處理邏輯和創意測試結果
|
||||
for (const result of testResults) {
|
||||
const user = userMap.get(result.user_id)
|
||||
if (user) {
|
||||
let details = null
|
||||
|
||||
// 根據測試類型獲取詳細資料
|
||||
if (result.test_type === 'logic') {
|
||||
const logicAnswers = await getLogicTestAnswersByTestResultId(result.id)
|
||||
if (logicAnswers.length > 0) {
|
||||
const logicAnswer = logicAnswers[0]
|
||||
details = {
|
||||
correctAnswers: logicAnswer.correct_answers,
|
||||
totalQuestions: logicAnswer.total_questions,
|
||||
accuracy: logicAnswer.accuracy
|
||||
}
|
||||
}
|
||||
} else if (result.test_type === 'creative') {
|
||||
const creativeAnswers = await getCreativeTestAnswersByTestResultId(result.id)
|
||||
if (creativeAnswers.length > 0) {
|
||||
const creativeAnswer = creativeAnswers[0]
|
||||
details = {
|
||||
dimensionScores: creativeAnswer.dimension_scores,
|
||||
totalScore: creativeAnswer.total_score,
|
||||
maxScore: creativeAnswer.max_score
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allResults.push({
|
||||
id: result.id,
|
||||
userId: result.user_id,
|
||||
userName: user.name,
|
||||
userDepartment: user.department,
|
||||
userEmail: user.email,
|
||||
type: result.test_type as 'logic' | 'creative',
|
||||
score: result.score,
|
||||
completedAt: result.completed_at,
|
||||
details
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 處理綜合測試結果
|
||||
for (const result of combinedResults) {
|
||||
const user = userMap.get(result.user_id)
|
||||
if (user) {
|
||||
allResults.push({
|
||||
id: result.id,
|
||||
userId: result.user_id,
|
||||
userName: user.name,
|
||||
userDepartment: user.department,
|
||||
userEmail: user.email,
|
||||
type: 'combined' as const,
|
||||
score: result.overall_score,
|
||||
completedAt: result.completed_at,
|
||||
details: {
|
||||
logicScore: result.logic_score,
|
||||
creativeScore: result.creativity_score, // 使用 creativity_score
|
||||
abilityBalance: result.balance_score, // 使用 balance_score
|
||||
breakdown: result.logic_breakdown
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 按完成時間排序(最新的在前)
|
||||
allResults.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())
|
||||
|
||||
// 應用篩選
|
||||
let filteredResults = allResults
|
||||
|
||||
// 搜尋篩選
|
||||
if (search) {
|
||||
filteredResults = filteredResults.filter(result =>
|
||||
result.userName.toLowerCase().includes(search.toLowerCase()) ||
|
||||
result.userEmail.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
// 部門篩選
|
||||
if (department !== "all") {
|
||||
filteredResults = filteredResults.filter(result => result.userDepartment === department)
|
||||
}
|
||||
|
||||
// 測試類型篩選
|
||||
if (testType !== "all") {
|
||||
filteredResults = filteredResults.filter(result => result.type === testType)
|
||||
}
|
||||
|
||||
// 生成 CSV 格式的資料
|
||||
const headers = [
|
||||
"用戶姓名",
|
||||
"用戶郵箱",
|
||||
"部門",
|
||||
"測試類型",
|
||||
"分數",
|
||||
"等級",
|
||||
"完成時間",
|
||||
"詳細資料"
|
||||
]
|
||||
|
||||
const data = filteredResults.map(result => {
|
||||
const getScoreLevel = (score: number) => {
|
||||
if (score >= 90) return "優秀"
|
||||
if (score >= 80) return "良好"
|
||||
if (score >= 70) return "中等"
|
||||
if (score >= 60) return "及格"
|
||||
return "待加強"
|
||||
}
|
||||
|
||||
const getTestTypeName = (type: string) => {
|
||||
switch (type) {
|
||||
case "logic": return "邏輯思維"
|
||||
case "creative": return "創意能力"
|
||||
case "combined": return "綜合能力"
|
||||
default: return "未知"
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString("zh-TW", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
})
|
||||
}
|
||||
|
||||
let detailsStr = ""
|
||||
if (result.details) {
|
||||
if (result.type === 'logic' && result.details.correctAnswers !== undefined) {
|
||||
detailsStr = `正確答案: ${result.details.correctAnswers}/${result.details.totalQuestions}, 準確率: ${result.details.accuracy}%`
|
||||
} else if (result.type === 'creative' && result.details.dimensionScores) {
|
||||
detailsStr = `總分: ${result.details.totalScore}/${result.details.maxScore}, 維度分數: ${JSON.stringify(result.details.dimensionScores)}`
|
||||
} else if (result.type === 'combined' && result.details.logicScore !== undefined) {
|
||||
const logicScore = result.details.logicScore ?? '無資料'
|
||||
const creativeScore = result.details.creativeScore ?? '無資料'
|
||||
const abilityBalance = result.details.abilityBalance ?? '無資料'
|
||||
detailsStr = `邏輯: ${logicScore}, 創意: ${creativeScore}, 平衡: ${abilityBalance}`
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
result.userName,
|
||||
result.userEmail,
|
||||
result.userDepartment,
|
||||
getTestTypeName(result.type),
|
||||
result.score,
|
||||
getScoreLevel(result.score),
|
||||
formatDate(result.completedAt),
|
||||
detailsStr
|
||||
]
|
||||
})
|
||||
|
||||
// 轉換為 CSV 格式
|
||||
const csvRows = [headers, ...data].map(row =>
|
||||
row.map(cell => {
|
||||
const escaped = String(cell).replace(/"/g, '""')
|
||||
return `"${escaped}"`
|
||||
}).join(",")
|
||||
)
|
||||
|
||||
const csvContent = csvRows.join("\n")
|
||||
|
||||
// 直接使用 UTF-8 BOM 字節
|
||||
const bomBytes = new Uint8Array([0xEF, 0xBB, 0xBF]) // UTF-8 BOM
|
||||
const contentBytes = new TextEncoder().encode(csvContent)
|
||||
const result = new Uint8Array(bomBytes.length + contentBytes.length)
|
||||
result.set(bomBytes)
|
||||
result.set(contentBytes, bomBytes.length)
|
||||
|
||||
const base64Content = Buffer.from(result).toString('base64')
|
||||
|
||||
return new NextResponse(JSON.stringify({
|
||||
success: true,
|
||||
data: base64Content,
|
||||
filename: `測驗結果_${new Date().toISOString().split('T')[0]}.csv`,
|
||||
contentType: "text/csv; charset=utf-8"
|
||||
}), {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error("匯出測驗結果失敗:", error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "匯出失敗", error: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user