Files
resume/templates/admin/quiz-record-detail.html
T

270 lines
14 KiB
HTML

<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="border-b border-slate-200 px-6 py-4 bg-slate-50">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<a href="{{.AdminPath}}/user/{{.User.ID}}/quiz?tab=records" class="text-slate-400 hover:text-slate-600 transition-colors">
<i class="fas fa-arrow-left"></i>
</a>
<div>
<h3 class="font-semibold text-slate-800 flex items-center gap-2">
<i class="fas fa-file-alt text-blue-500"></i> {{ if .Record.Title }}{{.Record.Title}}{{ else }}答题详情{{ end }}
</h3>
<p class="text-sm text-slate-500 mt-1">{{ if .User.Name }}{{.User.Name}}{{ else }}{{.User.Username}}{{ end }} 的答题记录</p>
</div>
</div>
<div class="flex items-center gap-6">
<div class="text-center">
<p class="text-xs text-slate-500 mb-1">答题时间</p>
<p class="text-sm font-medium text-slate-700">{{.Record.CreatedAt}}</p>
</div>
<div class="text-center">
<p class="text-xs text-slate-500 mb-1">得分</p>
<p class="text-3xl font-bold {{ if ge .Record.Score 60.0 }}text-green-600{{ else }}text-red-600{{ end }}">{{.Record.Score}}<span class="text-sm font-normal text-slate-400 ml-1"></span></p>
</div>
<div class="text-center">
<p class="text-xs text-slate-500 mb-1">正确率</p>
<p class="text-sm font-medium text-slate-700">{{.Record.CorrectCount}}/{{.Record.TotalCount}} 题</p>
</div>
</div>
</div>
</div>
<div class="p-6">
<div class="space-y-4">
{{ range $index, $q := .Questions }}
<div class="border border-slate-200 rounded-xl overflow-hidden" data-question-index="{{$index}}">
<div class="flex items-center justify-between px-4 py-3 bg-slate-50 border-b border-slate-200">
<div class="flex items-center gap-3">
<span class="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center text-sm font-medium text-slate-600">{{ add $index 1 }}</span>
<div class="flex items-center gap-2">
{{ if eq $q.Type "mcq" }}<span class="px-2 py-0.5 bg-blue-100 text-blue-600 rounded text-xs">选择题</span>{{ end }}
{{ if eq $q.Type "fill" }}<span class="px-2 py-0.5 bg-green-100 text-green-600 rounded text-xs">填空题</span>{{ end }}
{{ if eq $q.Type "sa" }}<span class="px-2 py-0.5 bg-purple-100 text-purple-600 rounded text-xs">简答题</span>{{ end }}
{{ if eq $q.Type "algo" }}<span class="px-2 py-0.5 bg-orange-100 text-orange-600 rounded text-xs">算法题</span>{{ end }}
{{ if $q.Difficulty }}
<span class="px-2 py-0.5 rounded text-xs"
{{ if eq $q.Difficulty "入门" }}style="background-color: #dcfce7; color: #166534;"{{ end }}
{{ if eq $q.Difficulty "初级" }}style="background-color: #dbeafe; color: #1e40af;"{{ end }}
{{ if eq $q.Difficulty "中级" }}style="background-color: #fef3c7; color: #92400e;"{{ end }}
{{ if eq $q.Difficulty "进阶" }}style="background-color: #ffedd5; color: #c2410c;"{{ end }}
{{ if eq $q.Difficulty "高级" }}style="background-color: #fee2e2; color: #991b1b;"{{ end }}>
{{ $q.Difficulty }}
</span>
{{ end }}
{{ if $q.Category }}<span class="px-2 py-0.5 bg-indigo-100 text-indigo-600 rounded text-xs">{{$q.Category}}</span>{{ end }}
</div>
</div>
<div class="flex items-center gap-3">
<button class="toggle-favorite-btn flex items-center gap-1 px-3 py-1.5 rounded-lg text-sm transition-all hover:bg-slate-100"
data-index="{{$index}}" data-user-id="{{$.User.ID}}" data-resume-id="{{$.Record.ResumeID}}" data-resume-route="{{$.Record.ResumeRoute}}">
<i class="fas fa-star text-slate-400"></i>
<span class="text-slate-500">收藏</span>
</button>
<span class="px-2 py-1 rounded-full text-xs font-medium {{ if index $.Correct $index }}bg-green-100 text-green-700{{ else }}bg-red-100 text-red-700{{ end }}">
{{ if index $.Correct $index }}回答正确{{ else }}回答错误{{ end }}
</span>
</div>
</div>
<div class="p-5">
<p class="text-slate-800 font-medium mb-4">{{ $q.Text }}</p>
{{ if $q.Options }}
<div class="space-y-2 mb-4">
{{ range $optIndex, $opt := $q.Options }}
{{ $letter := printf "%c" (index "ABCDE" $optIndex) }}
{{ $isCorrect := eq $letter $q.Answer }}
{{ $userAnswer := index $.UserAnswers $index }}
{{ $isUserAns := eq $letter $userAnswer }}
<div class="flex items-center gap-3 p-3 rounded-lg {{ if $isCorrect }}bg-green-50 border border-green-200{{ else if and $isUserAns (not $isCorrect) }}bg-red-50 border border-red-200{{ else }}bg-slate-50{{ end }}">
<span class="w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium {{ if $isCorrect }}bg-green-500 text-white{{ else if and $isUserAns (not $isCorrect) }}bg-red-500 text-white{{ else }}bg-slate-200 text-slate-600{{ end }}">{{ $letter }}</span>
<span class="text-sm {{ if $isCorrect }}text-green-700 font-medium{{ else if and $isUserAns (not $isCorrect) }}text-red-700{{ else }}text-slate-600{{ end }}">{{ $opt }}</span>
{{ if $isCorrect }}<i class="fas fa-check text-green-500 ml-auto text-sm"></i>{{ end }}
{{ if and $isUserAns (not $isCorrect) }}<i class="fas fa-times text-red-500 ml-auto text-sm"></i>{{ end }}
</div>
{{ end }}
</div>
{{ end }}
<div class="flex items-center gap-6 mb-4 text-sm">
<div class="flex items-center gap-2">
<span class="text-slate-500">正确答案:</span>
<span class="font-medium text-green-600 markdown-answer">{{ $q.Answer }}</span>
</div>
{{ with index $.UserAnswers $index }}
<div class="flex items-center gap-2">
<span class="text-slate-500">你的答案:</span>
<span class="font-medium {{ if index $.Correct $index }}text-green-600{{ else }}text-red-600{{ end }}">{{ . }}</span>
</div>
{{ end }}
</div>
{{ if $q.Analysis }}
<div class="p-4 bg-blue-50 rounded-lg border border-blue-100">
<div class="font-medium text-blue-700 text-sm mb-2 flex items-center gap-2">
<i class="fas fa-lightbulb text-xs"></i> 答案解析
</div>
<div class="text-sm text-slate-600 markdown-body markdown-analysis">{{ $q.Analysis }}</div>
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.0/github-markdown.min.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
<style>
.markdown-body { background-color: transparent !important; font-size: 0.88rem; }
.markdown-body p { margin: 0.5em 0; }
.markdown-body pre { background: #1e293b; padding: 0.75rem; border-radius: 0.38rem; overflow-x: auto; }
.markdown-body code { background: rgba(0,0,0,0.06); padding: 0.12rem 0.38rem; border-radius: 0.25rem; font-size: 0.81rem; }
.markdown-body pre code { background: transparent; padding: 0; color: #e2e8f0; }
.toggle-favorite-btn.favorited i { color: #fbbf24; }
.toggle-favorite-btn.favorited span { color: #d97706; }
.toggle-favorite-btn.favorited { background-color: #fef3c7; }
</style>
<script>
function decodeUnicode(str) {
return str.replace(/\\u([0-9a-fA-F]{4})/g, function (_, hex) {
return String.fromCharCode(parseInt(hex, 16));
});
}
function escapeHtml(str) {
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
document.addEventListener('DOMContentLoaded', function () {
marked.setOptions({ breaks: true, gfm: true });
document.querySelectorAll('.markdown-analysis').forEach(function (el) {
let md = decodeUnicode(el.textContent || '');
if (!md.trim()) return;
md = escapeHtml(md);
el.innerHTML = DOMPurify.sanitize(marked.parse(md));
el.classList.add('markdown-body');
});
document.querySelectorAll('.markdown-answer').forEach(function (el) {
let md = decodeUnicode(el.textContent || '');
if (!md.trim()) return;
md = escapeHtml(md);
el.innerHTML = DOMPurify.sanitize(marked.parseInline(md));
});
document.querySelectorAll('.toggle-favorite-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
const userId = this.dataset.userId;
const resumeId = this.dataset.resumeId;
const resumeRoute = this.dataset.resumeRoute;
const questionEl = this.closest('[data-question-index]');
const questionType = questionEl.querySelector('[class*="bg-blue-100"], [class*="bg-green-100"], [class*="bg-purple-100"], [class*="bg-orange-100"]')?.textContent || '综合';
const questionText = questionEl.querySelector('.text-slate-800.font-medium')?.textContent || '';
const questionOptions = [];
questionEl.querySelectorAll('.space-y-2 .text-sm').forEach(function(opt) {
questionOptions.push(opt.textContent.trim());
});
const questionAnswer = questionEl.querySelector('.text-green-600.markdown-answer')?.textContent || '';
const questionAnalysis = questionEl.querySelector('.markdown-analysis')?.textContent || '';
const questionDifficulty = questionEl.querySelector('[style*="background-color"]')?.textContent || '初级';
const questionCategory = questionEl.querySelector('.bg-indigo-100')?.textContent || '综合';
const isCorrectEl = this.nextElementSibling;
const isCorrect = isCorrectEl.textContent.includes('正确');
const userAnswerEl = questionEl.querySelector('.text-red-600') || questionEl.querySelector('.text-green-600');
const userAnswer = userAnswerEl ? userAnswerEl.textContent.trim().replace('你的答案:', '') : '';
const questionData = {
type: questionType.includes('选择') ? 'mcq' :
questionType.includes('填空') ? 'fill' :
questionType.includes('简答') ? 'sa' : 'algo',
text: questionText,
options: questionOptions.length > 0 ? questionOptions : null,
answer: questionAnswer,
analysis: questionAnalysis,
score: 10,
keywords: [],
difficulty: questionDifficulty,
category: questionCategory
};
const payload = {
user_id: userId,
resume_id: resumeId,
resume_route: resumeRoute,
question: questionData,
user_answer: userAnswer,
is_correct: isCorrect,
is_favorite: true
};
fetch('/api/quiz/favorite', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
if (data.success || data.code === 201) {
this.classList.add('favorited');
this.querySelector('i').classList.remove('text-slate-400');
this.querySelector('i').classList.add('text-yellow-500');
this.querySelector('span').textContent = '已收藏';
showToast('收藏成功');
} else {
showToast('收藏失败: ' + (data.message || '未知错误'));
}
})
.catch(error => {
showToast('收藏失败: ' + error.message);
});
});
});
});
function showToast(message) {
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 1.25rem;
right: 1.25rem;
padding: 0.75rem 1.50rem;
background: rgba(0,0,0,0.8);
color: white;
border-radius: 0.50rem;
z-index: 1000;
font-size: 0.88rem;
animation: slideIn 0.3s ease;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 2000);
}
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
</script>