refactor: 重新调整exam页面
This commit is contained in:
294
src/views/Exam/Index.vue
Normal file
294
src/views/Exam/Index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user