| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- {% extends "layout.html" %}
- {% block page_title %}审核题目{% endblock %}
- {% block content %}
- <div class="flex gap-6">
- <!-- 左侧目录索引 -->
- <div class="w-96 flex-shrink-0">
- <div class="apple-card p-6 sticky top-4 max-h-[calc(100vh-2rem)] overflow-y-auto">
- <div class="flex items-center justify-between mb-6 pb-3 border-b border-gray-200">
- <h2 class="text-lg font-bold text-gray-800">未审核知识点</h2>
- </div>
- <nav class="space-y-1">
- {% macro render_kp_node(node, level) %}
- <div class="kp-node-item mb-1" data-kp-code="{{ node.kp_code }}" data-level="{{ level }}">
- <div class="flex items-center gap-1 group">
- {% if node.children|length > 0 %}
- <button
- onclick="toggleKpNode('{{ node.kp_code }}'); event.stopPropagation();"
- class="w-6 h-6 rounded flex items-center justify-center text-gray-500 hover:text-orange-600 hover:bg-orange-50 transition-all flex-shrink-0 kp-expand-btn"
- data-expanded="{% if level == 0 %}true{% else %}false{% endif %}"
- data-kp-code="{{ node.kp_code }}">
- {% if level == 0 %}
- <i class="ri-subtract-line text-sm"></i>
- {% else %}
- <i class="ri-add-line text-sm"></i>
- {% endif %}
- </button>
- {% else %}
- <div class="w-6 h-6 flex items-center justify-center flex-shrink-0">
- <div class="w-1.5 h-1.5 rounded-full bg-gray-400"></div>
- </div>
- {% endif %}
- <a href="javascript:void(0)"
- onclick="loadPendingQuestionsByKp('{{ node.kp_code }}', '{{ node.name }}', this); return false;"
- data-kp-code="{{ node.kp_code }}"
- data-kp-name="{{ node.name }}"
- class="flex-1 block px-3 py-2 rounded-lg hover:bg-gradient-to-r hover:from-orange-50 hover:to-amber-50 transition-all border-l-2 border-transparent hover:border-orange-400 kp-link">
- <div class="flex items-center gap-2 flex-nowrap">
- {% if level == 0 %}
- <span class="bg-gradient-to-r from-slate-600 to-slate-700 text-white px-2 py-0.5 rounded text-xs font-bold shadow-sm flex-shrink-0">章</span>
- {% elif level == 1 %}
- <span class="bg-gradient-to-r from-blue-500 to-blue-600 text-white px-2 py-0.5 rounded text-xs font-bold shadow-sm flex-shrink-0">节</span>
- {% else %}
- <span class="bg-gray-200 text-gray-700 px-1.5 py-0.5 rounded text-xs font-medium flex-shrink-0">小节</span>
- {% endif %}
- <span class="text-sm {% if level == 0 %}font-bold text-gray-800{% elif level == 1 %}font-semibold text-gray-800{% else %}font-normal text-gray-600{% endif %} group-hover:text-orange-600 transition-colors flex-1 min-w-0 truncate">{{ node.name }}</span>
- {% set pending_count = node.total_pending_count|default(node.pending_count|default(0)) %}
- {% if pending_count > 0 %}
- <span class="bg-orange-100 text-orange-700 px-2 py-0.5 rounded-full text-xs font-bold flex-shrink-0 ml-1">{{ pending_count }}</span>
- {% endif %}
- </div>
- </a>
- </div>
- {% if node.children|length > 0 %}
- <div class="kp-children ml-8 mt-1 {% if level >= 1 %}hidden{% endif %}" id="kp-children-{{ node.kp_code }}" data-level="{{ level }}">
- {% for child in node.children %}
- {{ render_kp_node(child, level + 1) }}
- {% endfor %}
- </div>
- {% endif %}
- </div>
- {% endmacro %}
-
- <!-- 其他题目节点 -->
- {% if other_questions_count|default(0) > 0 %}
- <div class="kp-node-item mb-2 pb-2 border-b border-gray-200" data-kp-code="null" data-level="-1">
- <a href="javascript:void(0)"
- onclick="loadPendingQuestionsByKp('null', '其他题目', this); return false;"
- data-kp-code="null"
- data-kp-name="其他题目"
- class="flex-1 block px-3 py-2 rounded-lg hover:bg-gradient-to-r hover:from-purple-50 hover:to-pink-50 transition-all border-l-2 border-transparent hover:border-purple-400 kp-link">
- <div class="flex items-center gap-2 flex-nowrap">
- <span class="bg-gradient-to-r from-purple-500 to-pink-500 text-white px-2 py-0.5 rounded text-xs font-bold shadow-sm flex-shrink-0">其他</span>
- <span class="text-sm font-bold text-gray-800 group-hover:text-purple-600 transition-colors flex-1 min-w-0 truncate">其他题目</span>
- <span class="bg-orange-100 text-orange-700 px-2 py-0.5 rounded-full text-xs font-bold flex-shrink-0 ml-1">{{ other_questions_count }}</span>
- </div>
- </a>
- </div>
- {% endif %}
-
- {% if kp_tree %}
- {% for root_node in kp_tree %}
- {{ render_kp_node(root_node, 0) }}
- {% endfor %}
- {% else %}
- <div class="text-sm text-gray-500 text-center py-8">暂无未审核题目</div>
- {% endif %}
- </nav>
- </div>
- </div>
-
- <!-- 右侧题目列表区域 -->
- <div class="flex-1">
- <div id="questions-container" class="space-y-4">
- <div class="apple-card p-12 text-center">
- <div class="text-gray-400 mb-4">
- <i class="ri-file-check-line text-6xl"></i>
- </div>
- <h3 class="text-lg font-bold text-gray-600 mb-2">请选择左侧知识点</h3>
- <p class="text-sm text-gray-500 mb-6">点击知识点查看该知识点下的未审核题目</p>
- </div>
- </div>
- </div>
- </div>
- <script>
- // 知识点目录折叠/展开功能
- function toggleKpNode(kpCode) {
- const childrenContainer = document.getElementById(`kp-children-${kpCode}`);
- const expandBtn = document.querySelector(`.kp-expand-btn[data-kp-code="${kpCode}"]`);
-
- if (!childrenContainer || !expandBtn) return;
-
- const isExpanded = expandBtn.getAttribute('data-expanded') === 'true';
- const icon = expandBtn.querySelector('i');
-
- if (isExpanded) {
- // 折叠
- childrenContainer.classList.add('hidden');
- expandBtn.setAttribute('data-expanded', 'false');
- if (icon) {
- icon.className = 'ri-add-line text-sm';
- }
- } else {
- // 展开
- childrenContainer.classList.remove('hidden');
- expandBtn.setAttribute('data-expanded', 'true');
- if (icon) {
- icon.className = 'ri-subtract-line text-sm';
- }
- }
- }
- // 存储当前知识点的代码和名称
- let currentKpCode = null;
- let currentKpName = null;
- // 加载指定知识点的未审核题目列表
- function loadPendingQuestionsByKp(kpCode, kpName, linkElement) {
- const container = document.getElementById('questions-container');
- if (!container) return;
-
- // 保存当前知识点信息
- currentKpCode = kpCode;
- currentKpName = kpName;
-
- // 更新选中状态
- document.querySelectorAll('.kp-link').forEach(link => {
- link.classList.remove('bg-orange-50', 'border-orange-400');
- });
- if (linkElement) {
- linkElement.classList.add('bg-orange-50', 'border-orange-400');
- }
-
- // 显示加载状态
- container.innerHTML = `
- <div class="apple-card p-12 text-center">
- <div class="text-orange-500 mb-4">
- <i class="ri-loader-4-line text-6xl animate-spin"></i>
- </div>
- <p class="text-gray-600">正在加载未审核题目...</p>
- </div>
- `;
-
- // 请求未审核题目列表
- fetch(`/api/pending_questions_by_kp/${encodeURIComponent(kpCode)}`)
- .then(response => response.json())
- .then(data => {
- if (!data.success) {
- throw new Error(data.error || '加载失败');
- }
-
- const questions = data.questions || [];
- const kpName = data.kp_name || kpCode;
-
- // 如果没有题目,显示提示信息
- if (questions.length === 0) {
- container.innerHTML = `
- <div class="apple-card p-12 text-center">
- <div class="text-gray-400 mb-4">
- <i class="ri-file-list-line text-6xl"></i>
- </div>
- <h3 class="text-lg font-bold text-gray-600 mb-2">该知识点下暂无未审核题目</h3>
- <p class="text-sm text-gray-500">所有题目已审核完成</p>
- </div>
- `;
- return;
- }
-
- // 有题目时,渲染题目列表
- let html = `<div class="mb-4 flex items-center justify-between">
- <h3 class="text-xl font-bold text-gray-800">${kpName} <span class="text-sm font-normal text-gray-500">(${questions.length} 题待审核)</span></h3>
- </div>`;
-
- questions.forEach(q => {
- // 审核状态标签(应该都是待审核)
- let auditBadge = '<span class="bg-orange-100 text-orange-700 px-2 py-0.5 rounded text-xs font-bold whitespace-nowrap">待审核</span>';
-
- // 难度标签
- let difficultyBadge = '';
- if (q.difficulty !== null && q.difficulty !== undefined) {
- const diff = parseFloat(q.difficulty);
- if (diff === 0.2 || diff === 0.4) {
- difficultyBadge = '<span class="bg-green-100 text-green-700 px-2 py-0.5 rounded text-xs font-bold whitespace-nowrap">筑基</span>';
- } else if (diff === 0.4 || diff === 0.6) {
- difficultyBadge = '<span class="bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded text-xs font-bold whitespace-nowrap">提分</span>';
- } else if (diff === 0.7 || diff === 0.8) {
- difficultyBadge = '<span class="bg-orange-100 text-orange-700 px-2 py-0.5 rounded text-xs font-bold whitespace-nowrap">培优</span>';
- }
- }
-
- // 题目类型标签
- let typeBadge = '';
- if (q.question_type) {
- const typeMap = {
- 'single': '单选题',
- 'multiple': '多选题',
- 'judge': '判断题',
- 'fill': '填空题',
- 'essay': '解答题'
- };
- const typeName = typeMap[q.question_type] || q.question_type;
- typeBadge = `<span class="bg-blue-100 text-blue-700 px-2 py-0.5 rounded text-xs font-medium whitespace-nowrap">${typeName}</span>`;
- }
-
- // 题干预览(限制长度)
- const stemPreview = q.stem ? (q.stem.length > 150 ? q.stem.substring(0, 150) + '...' : q.stem) : '无题干';
-
- html += `
- <div class="apple-card p-6 flex items-center justify-between hover:shadow-lg transition-all">
- <div class="flex-1 pr-8">
- <div class="flex items-center space-x-3 mb-2 flex-wrap gap-2">
- <span class="text-xs font-bold uppercase tracking-wider text-gray-400">#${q.question_code}</span>
- ${auditBadge}
- ${difficultyBadge}
- ${typeBadge}
- </div>
- <p class="text-gray-700 line-clamp-2 math-render">${stemPreview}</p>
- </div>
- <a href="/detail/${q.question_code}" class="btn-apple bg-gradient-to-r from-orange-500 to-orange-600 text-white hover:from-orange-600 hover:to-orange-700 shadow-lg shadow-orange-200 flex items-center gap-2 px-4 py-2.5 whitespace-nowrap">
- <i class="ri-file-check-line"></i>
- <span>去审核</span>
- </a>
- </div>
- `;
- });
-
- container.innerHTML = html;
-
- // 渲染数学公式
- if (window.renderMathInElement) {
- container.querySelectorAll('.math-render').forEach(el => {
- try {
- window.renderMathInElement(el, {
- delimiters: [
- {left: "$$", right: "$$", display: true},
- {left: "$", right: "$", display: false},
- {left: "\\(", right: "\\)", display: false},
- {left: "\\[", right: "\\]", display: true}
- ],
- throwOnError: false
- });
- } catch (e) {
- console.warn("数学公式渲染失败:", e);
- }
- });
- }
- })
- .catch(err => {
- container.innerHTML = `
- <div class="apple-card p-12 text-center">
- <div class="text-red-500 mb-4">
- <i class="ri-error-warning-line text-6xl"></i>
- </div>
- <h3 class="text-lg font-bold text-gray-600 mb-2">加载失败</h3>
- <p class="text-sm text-gray-500">${err.message || '未知错误'}</p>
- </div>
- `;
- });
- }
- // 页面加载完成后,初始化知识点目录
- document.addEventListener('DOMContentLoaded', function() {
- // 默认展开第一级节点
- const firstLevelNodes = document.querySelectorAll('.kp-node-item[data-level="0"]');
- firstLevelNodes.forEach(node => {
- const kpCode = node.getAttribute('data-kp-code');
- const expandBtn = node.querySelector('.kp-expand-btn');
- if (expandBtn && expandBtn.getAttribute('data-expanded') === 'true') {
- const childrenContainer = document.getElementById(`kp-children-${kpCode}`);
- if (childrenContainer) {
- childrenContainer.classList.remove('hidden');
- }
- }
- });
- });
- </script>
- <style>
- .line-clamp-2 {
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
- </style>
- {% endblock %}
|