refactor: 重新调整exam页面

This commit is contained in:
2025-08-11 17:01:24 +08:00
parent b09961473e
commit 6e84953740
6 changed files with 1469 additions and 1070 deletions

294
src/views/Exam/Index.vue Normal file
View File

@@ -0,0 +1,294 @@
<template>
<div class="min-h-screen bg-base-100 p-5">
<div class="max-w-7xl mx-auto">
<div
class="flex justify-between items-center mb-8 pb-6 border-b border-base-300"
>
<h1 class="text-3xl font-bold text-base-content">实验列表</h1>
</div>
<div
v-if="loading"
class="flex flex-col items-center justify-center min-h-[300px]"
>
<div class="loading loading-spinner loading-lg text-primary mb-4"></div>
<p class="text-base-content/70">正在加载实验列表...</p>
</div>
<div
v-else-if="error"
class="flex flex-col items-center justify-center min-h-[300px]"
>
<div class="alert alert-error max-w-md">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div>
<h3 class="font-bold">加载失败</h3>
<div class="text-xs">{{ error }}</div>
</div>
</div>
<button @click="refreshExams" class="btn btn-primary mt-4">重试</button>
</div>
<div v-else class="space-y-6">
<div
v-if="exams.length === 0 && !isAdmin"
class="flex flex-col items-center justify-center min-h-[300px] text-center"
>
<h3 class="text-xl font-semibold text-base-content/70 mb-2">
暂无实验
</h3>
<p class="text-base-content/50">
当前没有可用的实验请联系管理员添加实验内容
</p>
</div>
<div
v-else
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
<!-- 管理员添加实验卡片 -->
<div
v-if="isAdmin"
class="card bg-base-200 shadow-lg hover:shadow-xl transition-all duration-200 cursor-pointer hover:scale-[1.02]"
@click="showCreateModal = true"
>
<div class="card-body flex items-center justify-center text-center">
<div class="text-primary text-6xl mb-4">+</div>
<h3 class="text-lg font-semibold text-primary">添加新实验</h3>
<p class="text-sm text-primary/70">点击创建新的实验</p>
</div>
</div>
<div
v-for="exam in exams"
:key="exam.id"
class="card bg-base-200 shadow-lg hover:shadow-xl transition-all duration-200 cursor-pointer hover:scale-[1.02] relative overflow-hidden"
@click="viewExam(exam.id)"
>
<div class="card-body">
<div class="flex justify-between items-start mb-4">
<h3 class="card-title text-base-content">{{ exam.name }}</h3>
<span
class="card-title text-sm text-blue-600/50 dark:text-sky-400/50"
>{{ exam.id }}</span
>
</div>
<!-- 实验标签 -->
<div
v-if="exam.tags && exam.tags.length > 0"
class="flex flex-wrap gap-1 mb-3"
>
<span
v-for="tag in exam.tags"
:key="tag"
class="badge badge-outline badge-sm"
>{{ tag }}</span
>
</div>
<div class="space-y-2 text-sm text-base-content/70">
<div class="flex items-center gap-2">
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span>创建{{ formatDate(exam.createdTime) }}</span>
</div>
<div class="flex items-center gap-2">
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>更新{{ formatDate(exam.updatedTime) }}</span>
</div>
</div>
</div>
<!-- 难度书角标识 -->
<div
class="difficulty-corner"
:class="{
'difficulty-1': exam.difficulty === 1,
'difficulty-2': exam.difficulty === 2,
'difficulty-3': exam.difficulty === 3,
'difficulty-4': exam.difficulty === 4,
'difficulty-5': exam.difficulty === 5,
}"
></div>
</div>
</div>
</div>
</div>
<!-- 实验详情模态框 -->
<ExamInfoModal
v-if="selectedExam"
v-model:show="showInfoModal"
:selectedExam="selectedExam"
/>
<!-- 创建实验模态框 -->
<ExamEditModal
v-model:show="showCreateModal"
@create-finished="handleCreateExamFinished"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { useRoute } from "vue-router";
import { AuthManager } from "@/utils/AuthManager";
import { type ExamSummary, type ExamInfo } from "@/APIClient";
import { formatDate } from "@/utils/Common";
import ExamInfoModal from "./ExamInfoModal.vue";
import ExamEditModal from "./ExamEditModal.vue";
// 响应式数据
const route = useRoute();
const exams = ref<ExamSummary[]>([]);
const selectedExam = ref<ExamInfo | null>(null);
const loading = ref(false);
const error = ref<string>("");
const isAdmin = ref(false);
// Modal
const showCreateModal = ref(false);
const showInfoModal = ref(false);
// 方法
const checkAdminStatus = async () => {
console.log("检查管理员权限...");
try {
isAdmin.value = await AuthManager.verifyAdminAuth();
console.log("管理员权限:", isAdmin.value);
} catch (err) {
console.warn("无法验证管理员权限:", err);
isAdmin.value = false;
}
};
const refreshExams = async () => {
loading.value = true;
error.value = "";
try {
const client = AuthManager.createAuthenticatedExamClient();
exams.value = await client.getExamList();
} catch (err: any) {
error.value = err.message || "获取实验列表失败";
console.error("获取实验列表失败:", err);
} finally {
loading.value = false;
}
};
const viewExam = async (examId: string) => {
try {
const client = AuthManager.createAuthenticatedExamClient();
selectedExam.value = await client.getExam(examId);
showInfoModal.value = true;
} catch (err: any) {
error.value = err.message || "获取实验详情失败";
console.error("获取实验详情失败:", err);
}
};
async function handleCreateExamFinished() {
await refreshExams();
}
// 生命周期
onMounted(async () => {
await checkAdminStatus();
await refreshExams();
// 处理路由参数如果有examId则自动打开该实验的详情模态框
const examId = route.query.examId as string;
if (examId) {
await viewExam(examId);
}
});
</script>
<style scoped>
/* 难度书角样式 */
.difficulty-corner {
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
pointer-events: none;
z-index: 10;
}
.difficulty-corner::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
border-style: solid;
border-width: 0 48px 48px 0;
transition: all 0.3s ease;
}
/* 难度颜色渐变:绿色到红色 */
.difficulty-1::before {
border-color: transparent transparent rgba(6, 199, 77, 0.6) transparent; /* 绿色 80% 透明度 */
}
.difficulty-2::before {
border-color: transparent transparent rgba(127, 204, 11, 0.6) transparent; /* 黄绿色 80% 透明度 */
}
.difficulty-3::before {
border-color: transparent transparent rgba(255, 191, 0, 0.6) transparent; /* 黄色 80% 透明度 */
}
.difficulty-4::before {
border-color: transparent transparent rgba(255, 106, 0, 0.6) transparent; /* 橙色 80% 透明度 */
}
.difficulty-5::before {
border-color: transparent transparent rgba(245, 35, 35, 0.6) transparent; /* 红色 80% 透明度 */
}
/* 悬停效果 */
.card:hover .difficulty-corner::before {
filter: brightness(1.1);
}
</style>