Initial commit

This commit is contained in:
2025-09-25 12:30:25 +08:00
commit 2765d9df54
100 changed files with 16023 additions and 0 deletions

180
lib/utils/excel-parser.ts Normal file
View File

@@ -0,0 +1,180 @@
import * as XLSX from "xlsx"
export interface LogicQuestionImport {
id: number
question: string
optionA: string
optionB: string
optionC: string
optionD: string
correctAnswer: string
explanation?: string
}
export interface CreativeQuestionImport {
id: number
statement: string
category: "innovation" | "imagination" | "flexibility" | "originality"
isReverse: boolean
}
export interface ImportResult {
success: boolean
message: string
data?: LogicQuestionImport[] | CreativeQuestionImport[]
errors?: string[]
}
export function parseExcelFile(file: File, type: "logic" | "creative"): Promise<ImportResult> {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target?.result as ArrayBuffer)
const workbook = XLSX.read(data, { type: "array" })
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
if (type === "logic") {
const result = parseLogicQuestions(jsonData as any[][])
resolve(result)
} else {
const result = parseCreativeQuestions(jsonData as any[][])
resolve(result)
}
} catch (error) {
resolve({
success: false,
message: "檔案解析失敗,請檢查檔案格式",
errors: [error instanceof Error ? error.message : "未知錯誤"],
})
}
}
reader.onerror = () => {
resolve({
success: false,
message: "檔案讀取失敗",
errors: ["無法讀取檔案"],
})
}
reader.readAsArrayBuffer(file)
})
}
function parseLogicQuestions(data: any[][]): ImportResult {
const errors: string[] = []
const questions: LogicQuestionImport[] = []
// 跳過標題行
for (let i = 1; i < data.length; i++) {
const row = data[i]
if (!row || row.length < 7) continue
try {
const question: LogicQuestionImport = {
id: Number.parseInt(row[0]) || i,
question: row[1]?.toString() || "",
optionA: row[2]?.toString() || "",
optionB: row[3]?.toString() || "",
optionC: row[4]?.toString() || "",
optionD: row[5]?.toString() || "",
correctAnswer: row[6]?.toString() || "",
explanation: row[7]?.toString() || "",
}
// 驗證必填欄位
if (
!question.question ||
!question.optionA ||
!question.optionB ||
!question.optionC ||
!question.optionD ||
!question.correctAnswer
) {
errors.push(`${i + 1} 行:缺少必填欄位`)
continue
}
// 驗證正確答案格式
if (!["A", "B", "C", "D"].includes(question.correctAnswer.toUpperCase())) {
errors.push(`${i + 1} 行:正確答案必須是 A、B、C 或 D`)
continue
}
questions.push(question)
} catch (error) {
errors.push(`${i + 1} 行:資料格式錯誤`)
}
}
if (questions.length === 0) {
return {
success: false,
message: "沒有找到有效的題目資料",
errors,
}
}
return {
success: true,
message: `成功解析 ${questions.length} 道題目`,
data: questions,
errors: errors.length > 0 ? errors : undefined,
}
}
function parseCreativeQuestions(data: any[][]): ImportResult {
const errors: string[] = []
const questions: CreativeQuestionImport[] = []
// 跳過標題行
for (let i = 1; i < data.length; i++) {
const row = data[i]
if (!row || row.length < 4) continue
try {
const question: CreativeQuestionImport = {
id: Number.parseInt(row[0]) || i,
statement: row[1]?.toString() || "",
category: (row[2]?.toString().toLowerCase() as any) || "innovation",
isReverse: row[3]?.toString().toLowerCase() === "是" || row[3]?.toString().toLowerCase() === "true",
}
// 驗證必填欄位
if (!question.statement) {
errors.push(`${i + 1} 行:缺少陳述內容`)
continue
}
// 驗證類別
const validCategories = ["innovation", "imagination", "flexibility", "originality"]
if (!validCategories.includes(question.category)) {
errors.push(`${i + 1} 行:類別必須是 innovation、imagination、flexibility 或 originality`)
continue
}
questions.push(question)
} catch (error) {
errors.push(`${i + 1} 行:資料格式錯誤`)
}
}
if (questions.length === 0) {
return {
success: false,
message: "沒有找到有效的題目資料",
errors,
}
}
return {
success: true,
message: `成功解析 ${questions.length} 道題目`,
data: questions,
errors: errors.length > 0 ? errors : undefined,
}
}

54
lib/utils/permissions.ts Normal file
View File

@@ -0,0 +1,54 @@
import type { User } from "@/lib/hooks/use-auth"
export type Permission =
| "view_own_results"
| "view_all_results"
| "manage_users"
| "view_analytics"
| "take_tests"
| "export_data"
| "manage_questions" // 新增題目管理權限
export const rolePermissions: Record<User["role"], Permission[]> = {
user: ["view_own_results", "take_tests"],
admin: [
"view_own_results",
"view_all_results",
"manage_users",
"view_analytics",
"take_tests",
"export_data",
"manage_questions",
], // 為管理員新增題目管理權限
}
export function hasPermission(user: User | null, permission: Permission): boolean {
if (!user) return false
return rolePermissions[user.role].includes(permission)
}
export function requirePermission(user: User | null, permission: Permission): void {
if (!hasPermission(user, permission)) {
throw new Error(`權限不足:需要 ${permission} 權限`)
}
}
export function canAccessRoute(user: User | null, route: string): boolean {
if (!user) return false
// 公開路由
const publicRoutes = ["/", "/login", "/register"]
if (publicRoutes.includes(route)) return true
// 用戶路由
const userRoutes = ["/dashboard", "/tests", "/results", "/settings"]
if (userRoutes.some((r) => route.startsWith(r))) return true
// 管理員路由
const adminRoutes = ["/admin"]
if (adminRoutes.some((r) => route.startsWith(r))) {
return user.role === "admin"
}
return false
}

View File

@@ -0,0 +1,188 @@
export interface TestScore {
score: number
level: string
description: string
color: string
}
export function calculateLogicScore(correctAnswers: number, totalQuestions: number): TestScore {
const score = Math.round((correctAnswers / totalQuestions) * 100)
if (score >= 90) {
return {
score,
level: "优秀",
color: "bg-green-500",
description: "逻辑思维能力出色,具备卓越的分析和推理能力",
}
}
if (score >= 80) {
return {
score,
level: "良好",
color: "bg-blue-500",
description: "逻辑思维能力较强,能够有效分析和解决问题",
}
}
if (score >= 70) {
return {
score,
level: "中等",
color: "bg-yellow-500",
description: "逻辑思维能力一般,有一定的分析能力",
}
}
if (score >= 60) {
return {
score,
level: "及格",
color: "bg-orange-500",
description: "逻辑思维能力需要提升,建议加强训练",
}
}
return {
score,
level: "不及格",
color: "bg-red-500",
description: "逻辑思维能力有待加强,需要系统性训练",
}
}
export function calculateCreativityScore(totalScore: number, maxScore: number): TestScore {
const score = Math.round((totalScore / maxScore) * 100)
if (score >= 85) {
return {
score,
level: "极具创意",
color: "bg-purple-500",
description: "拥有卓越的创新思维和想象力,能够产生独特的解决方案",
}
}
if (score >= 75) {
return {
score,
level: "很有创意",
color: "bg-blue-500",
description: "具备较强的创造性思维能力,善于创新",
}
}
if (score >= 65) {
return {
score,
level: "有一定创意",
color: "bg-green-500",
description: "具有一定的创新潜力,可以进一步培养",
}
}
if (score >= 50) {
return {
score,
level: "创意一般",
color: "bg-yellow-500",
description: "创造性思维有待提升,建议多参与创新活动",
}
}
return {
score,
level: "缺乏创意",
color: "bg-red-500",
description: "需要培养创新思维能力,建议接受创意思维训练",
}
}
export function calculateCombinedScore(
logicScore: number,
creativityScore: number,
): {
overallScore: number
level: string
description: string
color: string
breakdown: {
logic: number
creativity: number
balance: number
}
} {
// 综合分数逻辑40% + 创意40% + 平衡性20%
const logicWeight = 0.4
const creativityWeight = 0.4
const balanceWeight = 0.2
// 计算平衡性分数(两项分数越接近,平衡性越高)
const scoreDiff = Math.abs(logicScore - creativityScore)
const balanceScore = Math.max(0, 100 - scoreDiff * 2)
const overallScore = Math.round(
logicScore * logicWeight + creativityScore * creativityWeight + balanceScore * balanceWeight,
)
let level: string
let description: string
let color: string
if (overallScore >= 90) {
level = "卓越"
color = "bg-gradient-to-r from-purple-500 to-blue-500"
description = "综合能力卓越,逻辑思维与创意能力并重,是理想的复合型人才"
} else if (overallScore >= 80) {
level = "优秀"
color = "bg-gradient-to-r from-blue-500 to-green-500"
description = "综合能力优秀,在逻辑思维和创意能力方面都有良好表现"
} else if (overallScore >= 70) {
level = "良好"
color = "bg-gradient-to-r from-green-500 to-yellow-500"
description = "综合能力良好,具备一定的逻辑思维和创意能力"
} else if (overallScore >= 60) {
level = "中等"
color = "bg-gradient-to-r from-yellow-500 to-orange-500"
description = "综合能力中等,建议针对性提升薄弱环节"
} else {
level = "待提升"
color = "bg-gradient-to-r from-orange-500 to-red-500"
description = "综合能力有待提升,建议系统性训练逻辑思维和创意能力"
}
return {
overallScore,
level,
description,
color,
breakdown: {
logic: logicScore,
creativity: creativityScore,
balance: Math.round(balanceScore),
},
}
}
export function getRecommendations(logicScore: number, creativityScore: number): string[] {
const recommendations: string[] = []
if (logicScore < 70) {
recommendations.push("建议加强逻辑思维训练,多做推理题和数学题")
recommendations.push("学习系统性思维方法,如思维导图、流程图等")
}
if (creativityScore < 70) {
recommendations.push("建议参与更多创意活动,如头脑风暴、设计思维工作坊")
recommendations.push("培养好奇心,多接触不同领域的知识和经验")
}
const scoreDiff = Math.abs(logicScore - creativityScore)
if (scoreDiff > 20) {
if (logicScore > creativityScore) {
recommendations.push("您的逻辑思维较强,建议平衡发展创意能力")
} else {
recommendations.push("您的创意能力较强,建议平衡发展逻辑思维")
}
}
if (logicScore >= 80 && creativityScore >= 80) {
recommendations.push("您具备优秀的综合能力,建议承担更多挑战性工作")
recommendations.push("可以考虑担任需要创新和分析并重的领导角色")
}
return recommendations
}