首次提交:初始化项目代码
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
<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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user