Files
exam/src/main/resources/templates/mkexam.html
Yakumo Hokori f56733b54b first commit
2026-03-04 21:27:21 +08:00

537 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>创建试卷</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #fafafa;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5rem;
}
.card {
background: white;
border-radius: 16px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #f0f0f0;
}
.card h2 {
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 3px solid #f59e0b;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: #f59e0b;
color: white;
}
.btn-primary:hover {
background: #d97706;
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(245, 158, 11, 0.3);
}
.btn-secondary {
background: #9ca3af;
color: white;
}
.btn-secondary:hover {
background: #6b7280;
}
.btn-success {
background: #fbbf24;
color: #333;
}
.btn-success:hover {
background: #f59e0b;
}
.nav-bar {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.exam-info {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.exam-info h3 {
color: #f59e0b;
margin-bottom: 12px;
font-size: 14px;
}
.uid-display {
font-family: 'Courier New', monospace;
font-size: 16px;
background: #fef3c7;
padding: 12px 16px;
border-radius: 8px;
word-break: break-all;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.copy-btn {
background: #f59e0b;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
flex-shrink: 0;
}
.copy-btn:hover {
background: #d97706;
}
.question-section {
margin-top: 20px;
}
.question-section h4 {
color: #333;
margin-bottom: 12px;
padding: 10px 16px;
background: #f59e0b;
color: white;
border-radius: 8px;
}
.question-list {
list-style: none;
}
.question-item {
padding: 16px;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: flex-start;
gap: 12px;
}
.question-item:last-child {
border-bottom: none;
}
.question-number {
background: #f59e0b;
color: white;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
margin-top: 2px;
}
.question-content {
flex: 1;
}
.question-title {
font-size: 15px;
color: #333;
line-height: 1.5;
margin-bottom: 6px;
}
.question-meta {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.question-id {
background: #e0e0e0;
color: #666;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-family: monospace;
}
.question-score {
background: #f59e0b;
color: white;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.question-type {
background: #fef3c7;
color: #d97706;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.loading {
display: none;
text-align: center;
padding: 40px;
}
.loading-spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #f59e0b;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.alert {
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 16px;
}
.alert-success {
background: #d4edda;
color: #155724;
}
.alert-error {
background: #f8d7da;
color: #721c24;
}
.create-section {
padding: 20px 0;
}
.create-section > p {
color: #666;
margin-bottom: 24px;
font-size: 14px;
text-align: center;
}
.create-form {
max-width: 500px;
margin: 0 auto;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #f59e0b;
}
.form-actions {
display: flex;
justify-content: center;
margin-top: 24px;
}
.create-btn {
padding: 14px 48px;
font-size: 16px;
}
.stats {
display: flex;
gap: 16px;
margin-top: 16px;
flex-wrap: wrap;
}
.stat-item {
background: white;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
flex: 1;
min-width: 120px;
text-align: center;
}
.stat-label {
color: #666;
font-size: 12px;
margin-bottom: 4px;
}
.stat-value {
color: #f59e0b;
font-size: 28px;
font-weight: bold;
}
.exam-title {
font-size: 18px;
color: #333;
padding: 12px 16px;
background: #fef3c7;
border-radius: 8px;
border-left: 4px solid #f59e0b;
}
.result-actions {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #e0e0e0;
}
</style>
</head>
<body>
<div class="container">
<h1>创建试卷</h1>
<div class="nav-bar">
<a th:href="@{/admin/exam}" class="btn btn-secondary">← 返回题库管理</a>
</div>
<!-- 创建按钮区域 -->
<div class="card" id="createSection">
<h2>生成新试卷</h2>
<div class="create-section">
<p>系统将自动从题库中随机选择题目的7:3比例组合选择题:简答题总分100分</p>
<div class="create-form">
<div class="form-group">
<label for="examTitle">试卷标题</label>
<input type="text" id="examTitle" placeholder="请输入试卷标题(可选)" maxlength="100">
</div>
<div class="form-actions">
<button class="btn btn-primary create-btn" onclick="createExam()">创建试卷</button>
</div>
</div>
</div>
</div>
<!-- 加载中 -->
<div class="loading card" id="loadingSection">
<div class="loading-spinner"></div>
<p>正在生成试卷...</p>
</div>
<!-- 试卷信息展示 -->
<div class="card" id="resultSection" style="display: none;">
<h2>试卷生成成功</h2>
<div class="exam-info">
<h3>试卷标题</h3>
<div class="exam-title" id="examTitleDisplay">-</div>
<h3 style="margin-top: 16px;">试卷UID</h3>
<div class="uid-display">
<span id="uidDisplay"></span>
<button class="copy-btn" onclick="copyUid()">复制</button>
</div>
<div class="stats">
<div class="stat-item">
<div class="stat-label">选择题数量</div>
<div class="stat-value" id="choiceCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">简答题数量</div>
<div class="stat-value" id="essayCount">0</div>
</div>
<div class="stat-item">
<div class="stat-label">总题数</div>
<div class="stat-value" id="totalCount">0</div>
</div>
</div>
</div>
<div class="question-section">
<h4>选择题列表</h4>
<ul class="question-list" id="choiceList"></ul>
</div>
<div class="question-section">
<h4>简答题列表</h4>
<ul class="question-list" id="essayList"></ul>
</div>
<div class="result-actions">
<button class="btn btn-success" onclick="createAnother()">再创建一份</button>
</div>
</div>
<!-- 错误提示 -->
<div class="alert alert-error" id="errorSection" style="display: none;"></div>
</div>
<script>
function createExam() {
// 获取试卷标题
const title = document.getElementById('examTitle').value.trim();
// 显示加载,隐藏创建按钮
document.getElementById('createSection').style.display = 'none';
document.getElementById('loadingSection').style.display = 'block';
document.getElementById('resultSection').style.display = 'none';
document.getElementById('errorSection').style.display = 'none';
// 构建表单数据
const formData = new FormData();
if (title) {
formData.append('title', title);
}
fetch('/admin/mkexam/create', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('创建试卷失败');
}
return response.json();
})
.then(data => {
// 获取试卷详情包含题目title
return fetch('/admin/mkexam/detail/' + data.uid);
})
.then(response => response.json())
.then(detail => {
displayExamDetail(detail);
})
.catch(error => {
showError(error.message);
});
}
function displayExamDetail(data) {
document.getElementById('loadingSection').style.display = 'none';
document.getElementById('resultSection').style.display = 'block';
// 显示试卷标题
const titleDisplay = document.getElementById('examTitleDisplay');
titleDisplay.textContent = data.title || '未命名试卷';
// 显示UID
document.getElementById('uidDisplay').textContent = data.uid;
// 更新统计
const choiceQuestions = data.choiceQuestions || [];
const essayQuestions = data.essayQuestions || [];
document.getElementById('choiceCount').textContent = choiceQuestions.length;
document.getElementById('essayCount').textContent = essayQuestions.length;
document.getElementById('totalCount').textContent = choiceQuestions.length + essayQuestions.length;
// 显示选择题列表
const choiceList = document.getElementById('choiceList');
choiceList.innerHTML = '';
if (choiceQuestions.length === 0) {
choiceList.innerHTML = '<li class="question-item"><div class="question-content">暂无选择题</div></li>';
} else {
choiceQuestions.forEach((q, index) => {
const li = document.createElement('li');
li.className = 'question-item';
li.innerHTML = `
<span class="question-number">${index + 1}</span>
<div class="question-content">
<div class="question-title">${escapeHtml(q.title)}</div>
<div class="question-meta">
<span class="question-id">ID: ${q.id}</span>
<span class="question-type">选择题</span>
<span class="question-score">${q.score || 0}分</span>
</div>
</div>
`;
choiceList.appendChild(li);
});
}
// 显示简答题列表
const essayList = document.getElementById('essayList');
essayList.innerHTML = '';
if (essayQuestions.length === 0) {
essayList.innerHTML = '<li class="question-item"><div class="question-content">暂无简答题</div></li>';
} else {
essayQuestions.forEach((q, index) => {
const li = document.createElement('li');
li.className = 'question-item';
li.innerHTML = `
<span class="question-number">${index + 1}</span>
<div class="question-content">
<div class="question-title">${escapeHtml(q.title)}</div>
<div class="question-meta">
<span class="question-id">ID: ${q.id}</span>
<span class="question-type">简答题</span>
<span class="question-score">${q.score || 0}分</span>
</div>
</div>
`;
essayList.appendChild(li);
});
}
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function showError(message) {
document.getElementById('loadingSection').style.display = 'none';
document.getElementById('createSection').style.display = 'block';
const errorSection = document.getElementById('errorSection');
errorSection.textContent = message;
errorSection.style.display = 'block';
}
function copyUid() {
const uid = document.getElementById('uidDisplay').textContent;
navigator.clipboard.writeText(uid).then(() => {
const btn = document.querySelector('.copy-btn');
btn.textContent = '已复制';
setTimeout(() => {
btn.textContent = '复制';
}, 2000);
});
}
function createAnother() {
document.getElementById('resultSection').style.display = 'none';
document.getElementById('createSection').style.display = 'block';
document.getElementById('errorSection').style.display = 'none';
// 清空标题输入框
document.getElementById('examTitle').value = '';
}
</script>
</body>
</html>