| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806 |
- {% extends "layout.html" %}
- {% block page_title %}知识点管理{% endblock %}
- {% block content %}
- <!-- 树状节点宏定义 -->
- {% macro render_tree_node(kp, level) %}
- <div
- class="kp-node relative"
- data-kp-id="{{ kp.id }}"
- data-kp-code="{{ kp.kp_code }}"
- data-parent="{{ kp.parent_kp_code or '' }}"
- data-level="{{ level }}"
- data-grade="{{ kp.grade or '' }}">
-
- <!-- 节点卡片 -->
- <div class="flex items-start gap-4 group">
- <!-- 展开/折叠按钮区域 -->
- <div class="flex items-center gap-2 pt-4 flex-shrink-0">
- {% if kp.children|length > 0 %}
- <button
- onclick="toggleTreeNode('{{ kp.kp_code }}'); event.stopPropagation();"
- class="w-8 h-8 rounded-full bg-white border-2 border-gray-300 hover:border-blue-500 flex items-center justify-center text-gray-600 hover:text-blue-600 transition-all expand-btn z-10 shadow-sm hover:shadow-md"
- data-expanded="{% if level == 0 %}true{% else %}false{% endif %}">
- {% 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-8 h-8 flex items-center justify-center">
- <div class="w-2 h-2 rounded-full bg-gray-400"></div>
- </div>
- {% endif %}
- </div>
-
- <!-- 节点内容卡片 -->
- <div class="flex-1 apple-card p-5 hover:shadow-xl transition-all duration-300 group-hover:border-blue-300 border-2 border-transparent rounded-xl {% if kp.children|length > 0 %}cursor-pointer{% endif %}" {% if kp.children|length > 0 %}onclick="handleCardClick(event, '{{ kp.kp_code }}')"{% endif %}>
- <div class="flex items-start justify-between">
- <div class="flex-1 min-w-0">
- <div class="flex items-center gap-3 mb-3 flex-wrap">
- <span class="px-3 py-1.5 rounded-lg text-xs font-bold font-mono bg-gradient-to-r {% if level == 0 %}from-blue-500 to-indigo-600{% elif level == 1 %}from-green-500 to-emerald-600{% else %}from-orange-500 to-amber-600{% endif %} text-white shadow-md">
- {{ kp.kp_code }}
- </span>
- <h3 class="text-lg font-bold text-gray-800 group-hover:text-blue-600 transition-colors">
- {{ kp.name }}
- </h3>
- </div>
-
- <div class="flex items-center gap-4 text-sm text-gray-500 mt-2 flex-wrap">
- {% if kp.grade %}
- <span class="flex items-center gap-1.5 px-2 py-1 bg-gray-50 rounded-md">
- <i class="ri-graduation-cap-line text-green-500"></i>
- <span>{{ kp.grade }}</span>
- </span>
- {% endif %}
- {% if kp.question_count is defined and kp.question_count > 0 %}
- <span class="flex items-center gap-1.5 px-2 py-1 bg-gradient-to-r from-red-50 to-orange-50 border border-red-200 rounded-md text-red-600 shadow-sm">
- <i class="ri-file-list-line text-red-500"></i>
- <span class="font-bold">{{ kp.question_count }} 道题目</span>
- </span>
- {% endif %}
- {% if kp.has_children %}
- <span class="flex items-center gap-1.5 px-2 py-1 bg-blue-50 rounded-md text-blue-600">
- <i class="ri-node-tree"></i>
- <span class="font-semibold">{{ kp.children|length }} 个子节点</span>
- </span>
- {% endif %}
- </div>
- </div>
-
- <!-- 操作按钮 -->
- <div class="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity ml-4 flex-shrink-0" onclick="event.stopPropagation()">
- <button
- onclick="showAddChildModal('{{ kp.kp_code }}', '{{ kp.name }}')"
- class="px-3 py-1.5 text-xs font-semibold text-white bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 rounded-lg transition-all shadow-sm hover:shadow-md flex items-center gap-1.5"
- title="添加子知识点">
- <i class="ri-add-line"></i>
- <span>添加子节点</span>
- </button>
- <button
- onclick="showEditModal({{ kp.id }})"
- class="w-9 h-9 rounded-lg text-blue-600 bg-blue-50 hover:bg-blue-100 flex items-center justify-center transition-colors shadow-sm hover:shadow-md"
- title="编辑">
- <i class="ri-edit-line"></i>
- </button>
- <button
- onclick="showDeleteConfirm({{ kp.id }}, '{{ kp.kp_code }}', '{{ kp.name }}')"
- class="w-9 h-9 rounded-lg text-red-600 bg-red-50 hover:bg-red-100 flex items-center justify-center transition-colors shadow-sm hover:shadow-md"
- title="删除">
- <i class="ri-delete-bin-line"></i>
- </button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 子节点容器 -->
- {% if kp.children|length > 0 %}
- <div class="children-container mt-3 ml-12 {% if level >= 1 %}hidden{% endif %}" id="children-{{ kp.kp_code }}" data-level="{{ level }}" data-parent-code="{{ kp.kp_code }}">
- {% for child in kp.children %}
- {{ render_tree_node(child, level + 1) }}
- {% endfor %}
- </div>
- {% endif %}
- </div>
- {% endmacro %}
- <div class="space-y-6">
- <!-- 学段统计卡片(只显示初中) -->
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
- {% for grade_name, info in grade_info.items() %}
- {# 只显示初中,小学和高中已注释 #}
- <div
- onclick="filterByGrade('{{ grade_name }}')"
- data-grade="{{ grade_name }}"
- class="grade-card apple-card p-6 cursor-pointer transform transition-all duration-300 hover:scale-[1.02] hover:shadow-xl group {{ info.bg }} border-2 border-transparent hover:border-gray-300">
- <div class="flex items-center justify-between mb-4">
- <div class="w-16 h-16 rounded-2xl bg-gradient-to-br {{ info.color }} flex items-center justify-center text-white text-2xl shadow-lg group-hover:scale-110 transition-transform group-hover:shadow-xl">
- <i class="{{ info.icon }}"></i>
- </div>
- <div class="text-right">
- <div class="text-3xl font-bold text-gray-800 group-hover:text-gray-900 transition-colors">{{ info.count }}</div>
- <div class="text-sm text-gray-500 mt-1 font-medium">知识点</div>
- </div>
- </div>
- <div class="flex items-center justify-between mb-3">
- <h3 class="text-xl font-bold text-gray-800 group-hover:text-gray-900 transition-colors">{{ grade_name }}</h3>
- <div class="w-8 h-8 rounded-full bg-white/60 flex items-center justify-center group-hover:bg-white/80 transition-colors">
- <i class="ri-arrow-right-line text-gray-600 group-hover:text-gray-800 transition-colors"></i>
- </div>
- </div>
- <div class="mt-4 h-1.5 bg-white/60 rounded-full overflow-hidden shadow-inner">
- {% set total_count = grade_info.values()|sum(attribute='count') %}
- {% if total_count > 0 %}
- {% set percentage = (info.count / total_count * 100)|round(1) %}
- {% else %}
- {% set percentage = 0 %}
- {% endif %}
- <div class="h-full bg-gradient-to-r {{ info.color }} rounded-full transition-all duration-500 shadow-sm"
- style="width: {{ percentage }}%"></div>
- </div>
- </div>
- {% endfor %}
- </div>
- <!-- 页面标题和操作按钮 -->
- <div class="flex items-center justify-between">
- <!-- 搜索框 -->
- <div class="flex-1 max-w-md">
- <div class="relative">
- <input
- type="text"
- id="kpSearchInput"
- placeholder="搜索知识点名称或代码..."
- class="w-full px-4 py-2 pl-10 pr-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
- onkeydown="handleSearchKeydown(event)"
- oninput="handleSearchInput(event)">
- <i class="ri-search-line absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
- <button
- id="clearSearchBtn"
- onclick="clearSearch()"
- class="hidden absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors">
- <i class="ri-close-line"></i>
- </button>
- </div>
- </div>
- <div class="flex items-center gap-3">
- <button
- onclick="clearFilter()"
- id="clearFilterBtn"
- class="hidden px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors flex items-center gap-2">
- <i class="ri-close-line"></i>
- <span>清除筛选</span>
- </button>
- <button
- onclick="showAddModal()"
- class="btn-apple bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-700 hover:to-indigo-700 px-4 py-2 rounded-lg shadow-sm flex items-center gap-2">
- <i class="ri-add-circle-line"></i>
- <span>添加知识点</span>
- </button>
- </div>
- </div>
- <!-- 知识点树状结构 -->
- <div class="space-y-3" id="kpTreeContainer">
- {% for root_kp in kp_tree %}
- {{ render_tree_node(root_kp, 0) }}
- {% endfor %}
- </div>
- </div>
- <!-- 添加/编辑知识点模态框 -->
- <div id="kpModal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
- <div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
- <div class="p-6 border-b border-gray-200">
- <h2 id="modalTitle" class="text-xl font-bold text-gray-800">添加知识点</h2>
- </div>
- <form id="kpForm" class="p-6 space-y-4">
- <input type="hidden" id="kpId" name="id">
-
- <div>
- <label class="block text-sm font-semibold text-gray-700 mb-2">知识点代码 *</label>
- <input
- type="text"
- id="kpCode"
- name="kp_code"
- required
- class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- placeholder="例如:M01">
- </div>
-
- <div>
- <label class="block text-sm font-semibold text-gray-700 mb-2">名称 *</label>
- <input
- type="text"
- id="kpName"
- name="name"
- required
- class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- placeholder="请输入知识点名称">
- </div>
-
- <div>
- <label class="block text-sm font-semibold text-gray-700 mb-2">年级</label>
- <input
- type="text"
- id="kpGrade"
- name="grade"
- class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- placeholder="例如:初中">
- </div>
-
- <!-- 科目字段隐藏,默认传"数学" -->
- <input type="hidden" id="kpSubject" name="subject" value="数学">
-
- <div>
- <label class="block text-sm font-semibold text-gray-700 mb-2">父知识点</label>
- <select
- id="kpParent"
- name="parent_kp_code"
- class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
- <option value="">无(顶级知识点)</option>
- {% for option in kp_options %}
- <option value="{{ option.kp_code }}">{{ option.kp_code }} - {{ option.name }}</option>
- {% endfor %}
- </select>
- </div>
-
- <div class="flex justify-end gap-3 pt-4 border-t border-gray-200">
- <button
- type="button"
- onclick="closeModal()"
- class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
- 取消
- </button>
- <button
- type="submit"
- class="px-4 py-2 bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition-colors">
- 保存
- </button>
- </div>
- </form>
- </div>
- </div>
- <script>
- // 知识点选项数据
- const kpOptions = {{ kp_options|tojson|safe }};
- // 显示添加模态框
- function showAddModal() {
- document.getElementById('modalTitle').textContent = '添加知识点';
- document.getElementById('kpForm').reset();
- document.getElementById('kpId').value = '';
- document.getElementById('kpCode').disabled = false;
- document.getElementById('kpSubject').value = '数学'; // 默认科目为数学
- document.getElementById('kpModal').classList.remove('hidden');
- }
- // 显示编辑模态框
- async function showEditModal(kpId) {
- try {
- const response = await fetch(`/api/kp/get/${kpId}`);
- const result = await response.json();
-
- if (result.success) {
- const kp = result.data;
- document.getElementById('modalTitle').textContent = '编辑知识点';
- document.getElementById('kpId').value = kp.id;
- document.getElementById('kpCode').value = kp.kp_code;
- document.getElementById('kpCode').disabled = true; // 编辑时不允许修改代码
- document.getElementById('kpName').value = kp.name || '';
- document.getElementById('kpSubject').value = kp.subject || '数学'; // 如果为空则默认为数学
- document.getElementById('kpGrade').value = kp.grade || '';
- document.getElementById('kpParent').value = kp.parent_kp_code || '';
- document.getElementById('kpModal').classList.remove('hidden');
- } else {
- if (window.customAlert) {
- window.customAlert('获取知识点信息失败: ' + result.error);
- } else {
- alert('获取知识点信息失败: ' + result.error);
- }
- }
- } catch (error) {
- console.error('Error:', error);
- if (window.customAlert) {
- window.customAlert('获取知识点信息失败: ' + error.message);
- } else {
- alert('获取知识点信息失败: ' + error.message);
- }
- }
- }
- // 关闭模态框
- function closeModal() {
- document.getElementById('kpModal').classList.add('hidden');
- }
- // 表单提交
- document.getElementById('kpForm').addEventListener('submit', async function(e) {
- e.preventDefault();
-
- const kpId = document.getElementById('kpId').value;
- const formData = {
- kp_code: (document.getElementById('kpCode').value || '').trim(),
- name: (document.getElementById('kpName').value || '').trim(),
- subject: (document.getElementById('kpSubject').value || '').trim() || '数学', // 默认科目为数学
- grade: (document.getElementById('kpGrade').value || '').trim() || null,
- parent_kp_code: (document.getElementById('kpParent').value || '').trim() || null
- };
-
- try {
- let response;
- if (kpId) {
- // 更新
- response = await fetch(`/api/kp/update/${kpId}`, {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify(formData)
- });
- } else {
- // 创建
- response = await fetch('/api/kp/create', {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify(formData)
- });
- }
-
- const result = await response.json();
-
- if (result.success) {
- if (window.customAlert) {
- window.customAlert(result.message || '操作成功', () => {
- window.location.reload();
- });
- } else {
- alert(result.message || '操作成功');
- window.location.reload();
- }
- } else {
- if (window.customAlert) {
- window.customAlert('操作失败: ' + result.error);
- } else {
- alert('操作失败: ' + result.error);
- }
- }
- } catch (error) {
- console.error('Error:', error);
- if (window.customAlert) {
- window.customAlert('操作失败: ' + error.message);
- } else {
- alert('操作失败: ' + error.message);
- }
- }
- });
- // 切换树节点展开/折叠
- function toggleTreeNode(kpCode) {
- const childrenContainer = document.getElementById(`children-${kpCode}`);
- if (!childrenContainer) return;
-
- // 找到对应的展开按钮(通过查找包含该按钮的节点)
- const kpNode = childrenContainer.closest('.kp-node');
- if (!kpNode) return;
-
- const expandBtn = kpNode.querySelector('.expand-btn');
- if (!expandBtn) return;
-
- const isExpanded = expandBtn.getAttribute('data-expanded') === 'true';
-
- if (isExpanded) {
- // 折叠:隐藏所有子节点
- childrenContainer.classList.add('hidden');
- expandBtn.setAttribute('data-expanded', 'false');
- const icon = expandBtn.querySelector('i');
- if (icon) {
- icon.className = 'ri-add-line text-sm';
- }
- } else {
- // 展开:显示直接子节点
- childrenContainer.classList.remove('hidden');
- expandBtn.setAttribute('data-expanded', 'true');
- const icon = expandBtn.querySelector('i');
- if (icon) {
- icon.className = 'ri-subtract-line text-sm';
- }
- }
- }
- // 强制展开节点(用于搜索时展开父节点)
- function expandTreeNode(kpCode) {
- const childrenContainer = document.getElementById(`children-${kpCode}`);
- if (!childrenContainer) return;
-
- const kpNode = childrenContainer.closest('.kp-node');
- if (!kpNode) return;
-
- const expandBtn = kpNode.querySelector('.expand-btn');
- if (!expandBtn) return;
-
- // 展开节点
- childrenContainer.classList.remove('hidden');
- expandBtn.setAttribute('data-expanded', 'true');
- const icon = expandBtn.querySelector('i');
- if (icon) {
- icon.className = 'ri-subtract-line text-sm';
- }
- }
- // 展开所有父节点直到根节点
- function expandAllParents(kpCode) {
- const kpNode = document.querySelector(`.kp-node[data-kp-code="${kpCode}"]`);
- if (!kpNode) return;
-
- let currentParentCode = kpNode.getAttribute('data-parent');
-
- // 递归展开所有父节点
- while (currentParentCode && currentParentCode !== '') {
- expandTreeNode(currentParentCode);
-
- // 获取父节点的父节点
- const parentNode = document.querySelector(`.kp-node[data-kp-code="${currentParentCode}"]`);
- if (parentNode) {
- currentParentCode = parentNode.getAttribute('data-parent');
- } else {
- break;
- }
- }
- }
- // 搜索知识点
- function searchKnowledgePoint(searchText) {
- if (!searchText || searchText.trim() === '') {
- clearSearch();
- return;
- }
-
- const searchLower = searchText.toLowerCase().trim();
- const allKpNodes = document.querySelectorAll('.kp-node');
- let foundNodes = [];
-
- // 清除之前的高亮
- allKpNodes.forEach(node => {
- const card = node.querySelector('.apple-card');
- if (card) {
- card.classList.remove('ring-4', 'ring-blue-500', 'ring-offset-2', 'bg-blue-50');
- }
- });
-
- // 搜索匹配的知识点(收集所有匹配的节点)
- for (const node of allKpNodes) {
- const kpCode = node.getAttribute('data-kp-code') || '';
- const kpName = node.querySelector('h3')?.textContent || '';
-
- // 模糊匹配:代码或名称包含搜索文本
- if (kpCode.toLowerCase().includes(searchLower) || kpName.toLowerCase().includes(searchLower)) {
- foundNodes.push({ node, kpCode, kpName });
- }
- }
-
- if (foundNodes.length === 0) {
- // 如果没有找到,显示提示
- if (window.customAlert) {
- window.customAlert('未找到匹配的知识点');
- } else {
- alert('未找到匹配的知识点');
- }
- return;
- }
-
- // 优先匹配代码,其次匹配名称
- foundNodes.sort((a, b) => {
- const aCodeMatch = a.kpCode.toLowerCase().includes(searchLower);
- const bCodeMatch = b.kpCode.toLowerCase().includes(searchLower);
- if (aCodeMatch && !bCodeMatch) return -1;
- if (!aCodeMatch && bCodeMatch) return 1;
- return 0;
- });
-
- // 跳转到第一个匹配的知识点
- const firstMatch = foundNodes[0];
- const targetNode = firstMatch.node;
- const targetKpCode = firstMatch.kpCode;
-
- // 展开所有父节点
- expandAllParents(targetKpCode);
-
- // 高亮当前节点
- const card = targetNode.querySelector('.apple-card');
- if (card) {
- card.classList.add('ring-4', 'ring-blue-500', 'ring-offset-2', 'bg-blue-50');
- }
-
- // 滚动到该节点(等待展开动画完成)
- setTimeout(() => {
- targetNode.scrollIntoView({ behavior: 'smooth', block: 'center' });
-
- // 3秒后移除高亮
- setTimeout(() => {
- if (card) {
- card.classList.remove('ring-4', 'ring-blue-500', 'ring-offset-2', 'bg-blue-50');
- }
- }, 3000);
- }, 200);
-
- // 如果有多个匹配,在控制台提示
- if (foundNodes.length > 1) {
- console.log(`找到 ${foundNodes.length} 个匹配的知识点,已跳转到第一个`);
- }
- }
- // 处理搜索输入(实时搜索)
- let searchTimeout = null;
- function handleSearchInput(event) {
- const searchText = event.target.value;
- const clearBtn = document.getElementById('clearSearchBtn');
-
- if (searchText.trim() !== '') {
- clearBtn.classList.remove('hidden');
- // 防抖:延迟500ms后执行搜索
- clearTimeout(searchTimeout);
- searchTimeout = setTimeout(() => {
- searchKnowledgePoint(searchText);
- }, 500);
- } else {
- clearBtn.classList.add('hidden');
- clearSearch();
- }
- }
- // 处理搜索框回车键(立即搜索)
- function handleSearchKeydown(event) {
- if (event.key === 'Enter') {
- event.preventDefault();
- clearTimeout(searchTimeout);
- const searchText = event.target.value;
- searchKnowledgePoint(searchText);
- }
- }
- // 清除搜索
- function clearSearch() {
- const searchInput = document.getElementById('kpSearchInput');
- const clearBtn = document.getElementById('clearSearchBtn');
-
- searchInput.value = '';
- clearBtn.classList.add('hidden');
-
- // 清除高亮
- const allKpNodes = document.querySelectorAll('.kp-node');
- allKpNodes.forEach(node => {
- const card = node.querySelector('.apple-card');
- if (card) {
- card.classList.remove('ring-4', 'ring-blue-500', 'ring-offset-2');
- }
- });
- }
- // 处理卡片点击事件(点击空白区域展开/折叠)
- function handleCardClick(event, kpCode) {
- // 如果点击的是按钮或链接,不触发展开/折叠
- if (event.target.closest('button') || event.target.closest('a')) {
- return;
- }
- // 触发展开/折叠
- toggleTreeNode(kpCode);
- }
- // 显示添加子节点模态框
- function showAddChildModal(parentKpCode, parentKpName) {
- document.getElementById('modalTitle').textContent = `添加子知识点(父节点:${parentKpCode} - ${parentKpName})`;
- document.getElementById('kpId').value = '';
- document.getElementById('kpCode').value = '';
- document.getElementById('kpName').value = '';
- document.getElementById('kpSubject').value = '数学'; // 默认科目为数学
- document.getElementById('kpGrade').value = '';
- document.getElementById('kpParent').value = parentKpCode;
- document.getElementById('kpCode').disabled = false;
- document.getElementById('kpModal').classList.remove('hidden');
- }
- // 删除确认
- function showDeleteConfirm(kpId, kpCode, kpName) {
- if (confirm(`确定要删除知识点 "${kpCode} - ${kpName}" 吗?\n\n注意:如果该知识点下有子知识点,将无法删除。`)) {
- deleteKp(kpId);
- }
- }
- // 删除知识点
- async function deleteKp(kpId) {
- try {
- const response = await fetch(`/api/kp/delete/${kpId}`, {
- method: 'POST',
- headers: {'Content-Type': 'application/json'}
- });
-
- const result = await response.json();
-
- if (result.success) {
- if (window.customAlert) {
- window.customAlert(result.message || '删除成功', () => {
- window.location.reload();
- });
- } else {
- alert(result.message || '删除成功');
- window.location.reload();
- }
- } else {
- if (window.customAlert) {
- window.customAlert('删除失败: ' + result.error);
- } else {
- alert('删除失败: ' + result.error);
- }
- }
- } catch (error) {
- console.error('Error:', error);
- if (window.customAlert) {
- window.customAlert('删除失败: ' + error.message);
- } else {
- alert('删除失败: ' + error.message);
- }
- }
- }
- // 点击模态框外部关闭
- document.getElementById('kpModal').addEventListener('click', function(e) {
- if (e.target === this) {
- closeModal();
- }
- });
- // 当前筛选的学段
- let currentFilterGrade = null;
- // 按学段筛选
- function filterByGrade(grade) {
- currentFilterGrade = grade;
-
- // 显示清除筛选按钮
- document.getElementById('clearFilterBtn').classList.remove('hidden');
-
- // 获取所有行
- const allRows = Array.from(document.querySelectorAll('.kp-row'));
-
- // 先隐藏所有行
- allRows.forEach(row => {
- row.classList.add('hidden');
- });
-
- // 显示匹配学段的行及其父节点
- allRows.forEach(row => {
- const rowGrade = row.getAttribute('data-grade');
- if (rowGrade === grade) {
- // 显示匹配的行
- row.classList.remove('hidden');
- // 确保父节点也显示
- showParentRows(row);
- }
- });
-
- // 重新计算可见行数
- const visibleCount = Array.from(document.querySelectorAll('.kp-row:not(.hidden)')).length;
- // 筛选信息已移除
-
- // 高亮选中的学段卡片
- document.querySelectorAll('.grade-card').forEach(card => {
- card.classList.remove('ring-2', 'ring-blue-500', 'ring-offset-2', 'border-blue-400');
- });
- // 找到被点击的卡片并高亮
- const clickedCard = document.querySelector(`.grade-card[data-grade="${grade}"]`);
- if (clickedCard) {
- clickedCard.classList.add('ring-2', 'ring-blue-500', 'ring-offset-2', 'border-blue-400');
- }
- }
- // 显示父节点
- function showParentRows(row) {
- const parentCode = row.getAttribute('data-parent');
- if (parentCode) {
- const parentRow = document.querySelector(`tr[data-kp-code="${parentCode}"]`);
- if (parentRow) {
- parentRow.classList.remove('hidden');
- showParentRows(parentRow);
- }
- }
- }
- // 清除筛选
- function clearFilter() {
- currentFilterGrade = null;
- document.getElementById('clearFilterBtn').classList.add('hidden');
-
- // 恢复默认显示状态(level 0 和 level 1)
- const allRows = Array.from(document.querySelectorAll('.kp-row'));
- allRows.forEach(row => {
- const level = parseInt(row.getAttribute('data-level'));
- if (level <= 1) {
- row.classList.remove('hidden');
- } else {
- row.classList.add('hidden');
- }
- });
-
- // 重置展开按钮状态
- document.querySelectorAll('.expand-btn').forEach(btn => {
- const row = btn.closest('tr');
- const level = parseInt(row.getAttribute('data-level'));
- if (level === 0) {
- btn.setAttribute('data-expanded', 'true');
- btn.innerHTML = '<i class="ri-arrow-down-s-line"></i>';
- } else {
- btn.setAttribute('data-expanded', 'false');
- btn.innerHTML = '<i class="ri-arrow-right-s-line"></i>';
- }
- });
-
- const totalCount = {{ knowledge_points|length }};
- // 筛选信息已移除
-
- // 移除卡片高亮
- document.querySelectorAll('.grade-card').forEach(card => {
- card.classList.remove('ring-2', 'ring-blue-500', 'ring-offset-2', 'border-blue-400');
- });
- }
- // 展开/折叠子知识点
- function toggleChildren(kpCode) {
- const row = document.querySelector(`tr[data-kp-code="${kpCode}"]`);
- if (!row) return;
-
- const btn = row.querySelector('.expand-btn');
- const isExpanded = btn.getAttribute('data-expanded') === 'true';
-
- // 递归函数:切换所有子节点的显示状态
- function toggleChildrenRecursive(parentCode, shouldShow) {
- const allRows = Array.from(document.querySelectorAll('.kp-row'));
- const parentRow = allRows.find(r => r.getAttribute('data-kp-code') === parentCode);
- if (!parentRow) return;
-
- const parentLevel = parseInt(parentRow.getAttribute('data-level'));
- const parentIndex = allRows.indexOf(parentRow);
-
- // 找到所有直接子节点
- for (let i = parentIndex + 1; i < allRows.length; i++) {
- const childRow = allRows[i];
- const childLevel = parseInt(childRow.getAttribute('data-level'));
- const childParent = childRow.getAttribute('data-parent');
-
- // 如果是直接子节点
- if (childLevel === parentLevel + 1 && childParent === parentCode) {
- if (shouldShow) {
- childRow.classList.remove('hidden');
- // 如果子节点是展开的,继续展开它的子节点
- const childBtn = childRow.querySelector('.expand-btn');
- if (childBtn && childBtn.getAttribute('data-expanded') === 'true') {
- toggleChildrenRecursive(childRow.getAttribute('data-kp-code'), true);
- }
- } else {
- childRow.classList.add('hidden');
- // 递归隐藏所有子节点
- toggleChildrenRecursive(childRow.getAttribute('data-kp-code'), false);
- }
- } else if (childLevel <= parentLevel) {
- // 遇到同级或上级节点,停止
- break;
- }
- }
- }
-
- // 切换显示状态
- toggleChildrenRecursive(kpCode, !isExpanded);
-
- // 更新按钮状态
- if (isExpanded) {
- btn.setAttribute('data-expanded', 'false');
- btn.innerHTML = '<i class="ri-arrow-right-s-line"></i>';
- } else {
- btn.setAttribute('data-expanded', 'true');
- btn.innerHTML = '<i class="ri-arrow-down-s-line"></i>';
- }
- }
- </script>
- {% endblock %}
|