<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>学习共享 - 算子开发效率平台</title>
<link rel="stylesheet" href="css/style.css">
<style>
* { box-sizing: border-box; }
.mindmap-node {
position: relative;
}
.mindmap-node-children {
margin-left: 24px;
padding-left: 16px;
border-left: 2px solid #fed7aa;
margin-top: 8px;
display: none;
}
.mindmap-node-children.expanded {
display: block;
}
.mindmap-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
background: #ffffff;
position: relative;
color: #374151;
flex-wrap: wrap;
}
.mindmap-item:hover {
background: #fff7ed;
}
.mindmap-item::before {
content: '';
position: absolute;
left: -20px;
top: 50%;
width: 16px;
height: 2px;
background: #fed7aa;
}
.mindmap-item.active {
background: #e85d4d;
color: #ffffff;
}
.mindmap-item.active::before {
background: #e85d4d;
}
.mindmap-toggle {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
background: #fed7aa;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
flex-shrink: 0;
}
.mindmap-toggle:hover {
background: #fdba74;
}
.mindmap-toggle.expanded {
background: #e85d4d;
color: #ffffff;
}
.mindmap-icon {
font-size: 14px;
flex-shrink: 0;
cursor: pointer;
padding: 2px 4px;
border-radius: 3px;
transition: all 0.2s;
}
.mindmap-icon:hover {
background: #fed7aa;
}
.mindmap-toggle-icon.expanded {
color: #e85d4d;
}
.mindmap-name {
flex: 1;
font-size: 14px;
font-weight: 500;
}
.mindmap-count {
font-size: 11px;
background: #f3f4f6;
color: #6b7280;
padding: 2px 6px;
border-radius: 4px;
}
.mindmap-item.active .mindmap-count {
background: #ffffff;
color: #e85d4d;
}
.mindmap-item-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.mindmap-actions {
display: flex;
gap: 2px;
opacity: 0;
transform: translateX(8px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: absolute;
right: 12px;
bottom: 4px;
}
.mindmap-item:hover .mindmap-actions {
opacity: 1;
transform: translateX(0);
}
.mindmap-action-btn {
background: linear-gradient(135deg, #ffffff, #f9fafb);
border: 1px solid #e5e7eb;
font-size: 10px;
cursor: pointer;
padding: 3px 5px;
border-radius: 4px;
opacity: 0.7;
transition: all 0.2s;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
display: flex;
align-items: center;
gap: 2px;
line-height: 1;
}
.mindmap-action-btn:hover {
opacity: 1;
background: #ffffff;
border-color: #e85d4d;
box-shadow: 0 2px 6px rgba(232, 93, 77, 0.15);
transform: scale(1.05);
}
.doc-item {
background: #ffffff;
border: 2px solid #f3f4f6;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
cursor: pointer;
text-decoration: none;
color: inherit;
display: block;
overflow: hidden;
}
.doc-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(180deg, #e85d4d, #f59e0b);
transform: scaleY(0);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.doc-item:hover::before {
transform: scaleY(1);
}
.doc-item:hover {
border-color: #fed7aa;
box-shadow: 0 8px 24px rgba(232, 93, 77, 0.15);
transform: translateY(-2px);
}
.doc-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.doc-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
flex-shrink: 0;
}
.doc-item:hover .doc-actions {
opacity: 1;
}
.doc-action-btn {
background: linear-gradient(135deg, #ffffff, #f9fafb);
border: 1px solid #e5e7eb;
font-size: 12px;
cursor: pointer;
padding: 4px 8px;
border-radius: 5px;
opacity: 0.75;
transition: all 0.2s;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
display: flex;
align-items: center;
gap: 3px;
}
.doc-action-btn:hover {
opacity: 1;
background: #ffffff;
border-color: #e85d4d;
box-shadow: 0 2px 6px rgba(232, 93, 77, 0.2);
transform: scale(1.05);
}
.doc-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #e85d4d;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.doc-desc {
margin: 0;
font-size: 14px;
color: #6b7280;
line-height: 1.6;
}
.empty-docs {
text-align: center;
padding: 60px;
color: #9ca3af;
}
.empty-docs-icon {
font-size: 48px;
margin-bottom: 16px;
}
.empty-docs-text {
font-size: 16px;
}
.timeline {
position: relative;
padding: 40px 0;
}
.timeline::before {
content: '';
position: absolute;
left: 20px;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(180deg, #e85d4d, #fed7aa);
border-radius: 2px;
}
.timeline-item {
position: relative;
margin-bottom: 32px;
padding-left: 60px;
}
.timeline-dot {
position: absolute;
left: 8px;
top: 0;
width: 28px;
height: 28px;
background: linear-gradient(135deg, #e85d4d, #f59e0b);
border: 4px solid #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #fff;
font-weight: 600;
z-index: 1;
box-shadow: 0 4px 12px rgba(232, 93, 77, 0.3);
}
.timeline-content {
background: #fff;
border: 2px solid #fed7aa;
border-radius: 12px;
padding: 20px;
position: relative;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transition: all 0.3s;
}
.timeline-content:hover {
border-color: #e85d4d;
box-shadow: 0 8px 24px rgba(232, 93, 77, 0.15);
transform: translateX(8px);
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.timeline-title {
font-size: 18px;
font-weight: 700;
color: #e85d4d;
margin: 0;
flex: 1;
}
.timeline-actions {
display: flex;
gap: 8px;
opacity: 0;
transition: opacity 0.2s;
}
.timeline-item:hover .timeline-actions {
opacity: 1;
}
.timeline-action-btn {
background: linear-gradient(135deg, #ffffff, #f9fafb);
border: 1px solid #e5e7eb;
font-size: 13px;
cursor: pointer;
padding: 6px 10px;
border-radius: 6px;
opacity: 0.8;
transition: all 0.2s;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
display: flex;
align-items: center;
gap: 4px;
}
.timeline-action-btn:hover {
opacity: 1;
background: #fff;
border-color: #e85d4d;
box-shadow: 0 2px 8px rgba(232, 93, 77, 0.2);
transform: scale(1.05);
}
.timeline-desc {
font-size: 14px;
color: #6b7280;
line-height: 1.6;
margin: 0;
}
.timeline-date {
font-size: 12px;
color: #92400e;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.timeline-date::before {
content: '📅';
font-size: 14px;
}
</style>
</head>
<body style="background:linear-gradient(to bottom, #ffffff 0%, #fff7ed 100%);min-height:100vh;margin:0;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
<header class="header">
<div class="container header-content">
<div class="header-left">
<a href="index.html" class="logo">
<div class="logo-icon">⚡</div>
<span class="logo-text">算子开发效率平台</span>
</a>
<nav class="nav">
<a href="index.html" class="nav-link">首页</a>
<a href="deployment.html" class="nav-link">环境部署</a>
<a href="learning.html" class="nav-link active">学习共享</a>
<a href="query.html" class="nav-link">材料查询</a>
</nav>
</div>
<div class="header-right">
<a href="index.html" class="btn btn-ghost">返回首页</a>
</div>
</div>
</header>
<main class="main">
<div class="container">
<div class="search-box" style="max-width: 1400px;margin:0 auto 24px;">
<div style="background:#fff;border-radius:12px;border:2px solid #e5e7eb;padding:16px;display:flex;align-items:center;gap:12px;box-shadow:0 2px 8px rgba(0,0,0,0.04);">
<span style="font-size:18px;">🔍</span>
<input type="text" id="searchInput" placeholder="搜索文档..." style="flex:1;border:none;background:transparent;font-size:14px;color:#374151;outline:none;" oninput="searchDocs()">
</div>
</div>
<div style="display:grid;grid-template-columns:280px 1fr;gap:24px;max-width:1400px;margin:32px auto 0;">
<div style="background:#fff;border-radius:12px;border:2px solid #fed7aa;box-shadow:0 4px 16px rgba(0,0,0,0.08);overflow:hidden;">
<div style="padding:20px;border-bottom:1px solid #f3f4f6;background:linear-gradient(135deg,#fff7ed,#ffedd5);display:flex;align-items:center;justify-content:space-between;">
<h3 style="margin:0;font-size:18px;font-weight:700;color:#92400e;">📁 分类</h3>
<button id="add-category-btn" style="padding:8px 14px;border:none;background:linear-gradient(135deg, #e85d4d, #f59e0b);color:#fff;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;box-shadow:0 2px 6px rgba(232, 93, 77, 0.2);transition:all 0.2s;white-space:nowrap;">
<span>+</span> 新增分类
</button>
</div>
<div id="categories" style="padding:16px;max-height:calc(100vh-240px);overflow-y:auto;"></div>
</div>
<div style="background:#fff;border-radius:12px;border:2px solid #fed7aa;box-shadow:0 4px 16px rgba(0,0,0,0.08);overflow:hidden;">
<div style="padding:20px;border-bottom:1px solid #f3f4f6;background:linear-gradient(to right,#fff7ed,#ffffff);display:flex;align-items:center;justify-content:space-between;">
<div>
<h2 id="panelTitle" style="margin:0;font-size:22px;font-weight:700;color:#e85d4d;">选择分类</h2>
<p id="panelDesc" style="margin:8px 0 0;font-size:14px;color:#6b7280;">从左侧选择分类查看文档</p>
</div>
<button id="btn-add-doc" onclick="openAddDocModal()" style="display:none;padding:10px 16px;border:none;background:linear-gradient(135deg, #e85d4d, #f59e0b);color:#fff;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(232, 93, 77, 0.2);transition:all 0.2s;white-space:nowrap;">
<span>+</span> 新增文档
</button>
</div>
<div id="documents" style="padding:20px;max-height:calc(100vh-240px);overflow-y:auto;"></div>
</div>
</div>
</div>
</main>
<div id="doc-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:1000;align-items:center;justify-content:center;">
<div style="background:#fff;border-radius:12px;padding:24px;width:90%;max-width:540px;box-shadow:0 8px 32px rgba(0,0,0,0.12);">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
<h3 id="doc-modal-title" style="margin:0;font-size:20px;font-weight:700;color:#e85d4d;">添加文档</h3>
<button id="doc-modal-close" style="background:none;border:none;font-size:24px;cursor:pointer;color:#6b7280;">×</button>
</div>
<div id="modal-form-content" style="display:flex;flex-direction:column;gap:16px;">
<input type="hidden" id="edit-doc-id">
<input type="hidden" id="doc-category-id">
<div id="title-field">
<label style="display:block;font-size:14px;font-weight:600;color:#374151;margin-bottom:6px;">标题 <span style="color:#ef4444;">*</span></label>
<input type="text" id="doc-title" placeholder="输入标题" style="width:100%;padding:10px 12px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;">
</div>
<div id="desc-field">
<label style="display:block;font-size:14px;font-weight:600;color:#374151;margin-bottom:6px;">描述 <span style="color:#ef4444;">*</span></label>
<textarea id="doc-desc" rows="3" placeholder="输入描述" style="width:100%;padding:10px 12px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;resize:vertical;"></textarea>
</div>
<div id="wiki-field">
<label style="display:block;font-size:14px;font-weight:600;color:#374151;margin-bottom:6px;">Wiki 链接 <span style="color:#ef4444;">*</span></label>
<input type="text" id="doc-wiki" placeholder="https://wiki.example.com/..." style="width:100%;padding:10px 12px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;">
<div style="font-size:12px;color:#6b7280;margin-top:4px;">点击文档卡片将跳转到此链接</div>
</div>
</div>
<div style="display:flex;gap:12px;margin-top:24px;">
<button id="doc-modal-cancel" style="flex:1;padding:12px;border:2px solid #e5e7eb;background:#fff;border-radius:8px;font-size:14px;font-weight:600;color:#374151;cursor:pointer;transition:all 0.2s;">取消</button>
<button id="doc-modal-save" style="flex:1;padding:12px;border:none;background:linear-gradient(135deg, #e85d4d, #f59e0b);color:#fff;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;box-shadow:0 4px 12px rgba(232, 93, 77, 0.3);transition:all 0.2s;">保存</button>
</div>
</div>
</div>
<script>
const defaultData = [
{
id: 1,
name: "版本演进时间图",
icon: "📅",
expanded: false,
children: [
{ id: 101, name: "v1.0 版本", icon: "1️⃣", items: [], children: [], expanded: false },
{ id: 102, name: "v2.0 版本", icon: "2️⃣", items: [], children: [], expanded: false },
{ id: 103, name: "v3.0 版本", icon: "3️⃣", items: [], children: [], expanded: false }
],
items: [
{id: 1, title:"算子框架发展历程",desc:"从 v1.0 到 v3.0 的完整演进历程",wiki:"https://wiki.example.com/1"},
{id: 2, title:"版本特性对比",desc:"各版本核心功能差异对比表",wiki:"https://wiki.example.com/2"},
{id: 3, title:"里程碑事件",desc:"算子发展历程中的关键节点",wiki:"https://wiki.example.com/3"}
]
},
{
id: 2,
name: "算子开发",
icon: "🔨",
expanded: false,
children: [],
items: [
{id: 4, title:"算子开发入门",desc:"算子的基本概念、开发流程和核心原理",wiki:"https://wiki.example.com/4"},
{id: 5, title:"开发环境搭建",desc:"完整的开发环境配置指南",wiki:"https://wiki.example.com/5"},
{id: 6, title:"Hello World 教程",desc:"从零开始编写第一个算子",wiki:"https://wiki.example.com/6"},
{id: 7, title:"API 接口说明",desc:"算子开发的核心 API 接口文档",wiki:"https://wiki.example.com/7"},
{id: 8, title:"数据类型与格式",desc:"支持的数据类型及格式规范",wiki:"https://wiki.example.com/8"}
]
},
{
id: 3,
name: "构建工程",
icon: "🏗️",
expanded: false,
children: [],
items: [
{id: 9, title:"工程结构说明",desc:"算子工程的标准目录结构和文件组织",wiki:"https://wiki.example.com/9"},
{id: 10, title:"构建系统配置",desc:"CMake、Bazel 等构建工具配置指南",wiki:"https://wiki.example.com/10"},
{id: 11, title:"依赖管理",desc:"第三方库的引入和管理方法",wiki:"https://wiki.example.com/build/dependencies"},
{id: 12, title:"编译优化选项",desc:"编译器的优化参数配置",wiki:"https://wiki.example.com/build/optimization"},
{id: 13, title:"多平台构建",desc:"Windows、Linux、macOS 跨平台构建",wiki:"https://wiki.example.com/build/crossplatform"}
]
},
{
id: 4,
name: "算子实现原理",
icon: "⚙️",
expanded: false,
children: [],
items: [
{id: 14, title:"算子内核原理",desc:"深入理解算子内核的执行机制",wiki:"https://wiki.example.com/11"},
{id: 15, title:"内存管理机制",desc:"算子执行过程中的内存分配与释放",wiki:"https://wiki.example.com/12"},
{id: 16, title:"并行计算策略",desc:"多线程和并行计算的实现方式",wiki:"https://wiki.example.com/13"},
{id: 17, title:"算子融合技术",desc:"多个算子融合执行的原理和优势",wiki:"https://wiki.example.com/14"},
{id: 18, title:"硬件加速优化",desc:"利用 GPU、NPU 等硬件加速算子",wiki:"https://wiki.example.com/15"},
{id: 19, title:"性能调优技巧",desc:"算子性能优化的实用技巧",wiki:"https://wiki.example.com/16"}
]
},
{
id: 5,
name: "测试验证",
icon: "✅",
expanded: false,
children: [],
items: [
{id: 20, title:"单元测试框架",desc:"算子单元测试的框架和工具",wiki:"https://wiki.example.com/17"},
{id: 21, title:"测试用例编写",desc:"高质量测试用例的编写方法",wiki:"https://wiki.example.com/18"},
{id: 22, title:"性能基准测试",desc:"算子性能基准测试和对比",wiki:"https://wiki.example.com/19"},
{id: 23, title:"正确性验证",desc:"算子输出正确性的验证方法",wiki:"https://wiki.example.com/20"},
{id: 24, title:"边界条件测试",desc:"极端情况下的算子行为验证",wiki:"https://wiki.example.com/21"},
{id: 25, title:"自动化测试流程",desc:"CI/CD 集成自动化测试方案",wiki:"https://wiki.example.com/22"}
]
}
];
let data = [];
let activeId = null;
let activeSubId = null;
let searchQuery = '';
let searchResults = {
categories: [],
items: [],
sourceCategoryId: null
};
function loadData() {
console.log('[LoadData] Starting to load data...');
const possibleKeys = ['learningCategories', 'operator_efficiency_learningCategories'];
let stored = null;
let usedKey = null;
for (const key of possibleKeys) {
stored = localStorage.getItem(key);
if (stored && stored !== 'null' && stored !== 'undefined') {
usedKey = key;
console.log('[LoadData] Found data in key:', key);
break;
}
}
if (stored && stored !== 'null' && stored !== 'undefined') {
try {
data = JSON.parse(stored);
console.log('[LoadData] Parsed data:', data);
if (!Array.isArray(data) || data.length === 0) {
console.log('[LoadData] Invalid data structure, using default');
data = JSON.parse(JSON.stringify(defaultData));
saveData();
}
} catch (e) {
console.error('[LoadData] Failed to parse data, using default:', e);
data = JSON.parse(JSON.stringify(defaultData));
saveData();
}
} else {
console.log('[LoadData] No data found, using default');
data = JSON.parse(JSON.stringify(defaultData));
saveData();
}
console.log('[LoadData] Final data:', data);
}
function saveData() {
localStorage.setItem('learningCategories', JSON.stringify(data));
}
function findNode(id) {
for (let i = 0; i < data.length; i++) {
if (data[i].id === id) {
return { node: data[i], parent: null, index: i, type: 'main' };
}
}
function findInChildren(nodes, parent = null, grandParent = null) {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].id === id) {
return {
node: nodes[i],
parent: parent,
grandParent: grandParent,
index: i,
type: 'sub',
siblingsArray: parent ? parent.children : nodes
};
}
if (nodes[i].children && nodes[i].children.length > 0) {
const result = findInChildren(nodes[i].children, nodes[i], parent);
if (result) return result;
}
}
return null;
}
return findInChildren(data);
}
function renderMindmapNode(category, level) {
const hasChildren = category.children && category.children.length > 0;
const isExpanded = category.expanded;
const itemCount = category.items ? category.items.length : 0;
const isActive = (category.id === activeId && level === 0) || (category.id === activeSubId && level > 0);
let html = '<div class="mindmap-node" style="margin-left: ' + (level * 16) + 'px;">';
html += '<div class="mindmap-item' + (isActive ? ' active' : '') + '" onclick="selectCategory(' + category.id + ', ' + level + ')">';
html += '<div class="mindmap-item-content">';
if (hasChildren) {
html += '<span class="mindmap-icon mindmap-toggle-icon' + (isExpanded ? ' expanded' : '') + '" onclick="toggleExpand(event, ' + category.id + ', ' + level + ')">' + (isExpanded ? '▼' : '▶') + '</span>';
} else {
html += '<span class="mindmap-icon">●</span>';
}
html += '<span class="mindmap-name">' + category.name + '</span>';
if (itemCount > 0) {
html += '<span class="mindmap-count">' + itemCount + '篇</span>';
}
html += '</div>';
html += '<div class="mindmap-actions" onclick="event.stopPropagation()">';
html += '<button class="mindmap-action-btn" onclick="openAddCategoryModal(' + category.id + ', ' + level + ')" title="添加子类">➕</button>';
html += '<button class="mindmap-action-btn" onclick="openEditCategoryModal(' + category.id + ', ' + level + ')" title="编辑">✏️</button>';
html += '<button class="mindmap-action-btn" onclick="deleteCategory(' + category.id + ', ' + level + ')" title="删除">🗑️</button>';
html += '</div>';
html += '</div>';
if (hasChildren) {
html += '<div class="mindmap-node-children' + (isExpanded ? ' expanded' : '') + '">';
category.children.forEach(function(child) {
html += renderMindmapNode(child, level + 1);
});
html += '</div>';
}
html += '</div>';
return html;
}
function renderCategories() {
const container = document.getElementById("categories");
if (!container) {
console.error('[RenderCategories] Container not found!');
return;
}
let html = '';
data.forEach(function(category) {
html += renderMindmapNode(category, 0);
});
container.innerHTML = html;
console.log('[RenderCategories] Done, rendered ' + data.length + ' categories');
}
function renderDocuments() {
const container = document.getElementById("documents");
const titleEl = document.getElementById("panelTitle");
const descEl = document.getElementById("panelDesc");
if (!container || !titleEl) return;
if (searchQuery && (searchResults.categories.length > 0 || searchResults.items.length > 0)) {
titleEl.textContent = '搜索结果';
descEl.textContent = '找到 ' + (searchResults.categories.length + searchResults.items.length) + ' 个匹配项';
let html = '';
if (searchResults.categories.length > 0) {
html += '<div style="margin-bottom:20px;"><p style="font-size:14px;color:#6b7280;margin-bottom:12px;">匹配的分类:</p>';
html += searchResults.categories.map(function(cat) {
return '<div class="doc-item" onclick="selectCategory(' + cat.id + ', 0)" style="cursor:pointer;padding:16px;border:2px solid #fed7aa;border-radius:8px;margin-bottom:8px;background:#fff;">' +
'<span style="font-size:18px;font-weight:600;color:#e85d4d;">' + cat.name + '</span>' +
'<span style="font-size:12px;color:#6b7280;margin-left:8px;">(' + (cat.items ? cat.items.length : 0) + ' 篇文档)</span>' +
'</div>';
}).join('');
html += '</div>';
}
if (searchResults.items.length > 0) {
html += '<div><p style="font-size:14px;color:#6b7280;margin-bottom:12px;">匹配的文档:</p>';
html += searchResults.items.map(function(item) {
return '<a href="' + item.wiki + '" target="_blank" class="doc-item">' +
'<div class="doc-header">' +
'<h3 class="doc-title">' + item.title + '</h3>' +
'<div style="font-size:12px;color:#6b7280;font-weight:400;">来自:' + item.categoryName + '</div>' +
'<div class="doc-actions">' +
'<button class="doc-action-btn" onclick="openEditDocModal(event, ' + item.id + ', ' + item.index + ')" title="编辑">✏️</button>' +
'<button class="doc-action-btn" onclick="deleteDoc(event, ' + item.id + ')" title="删除">🗑️</button>' +
'</div>' +
'</div>' +
'<div class="doc-content">' +
'<p class="doc-desc">' + item.desc + '</p>' +
'</div>' +
'</a>';
}).join('');
html += '</div>';
}
container.innerHTML = html;
return;
}
if (searchQuery) {
titleEl.textContent = '搜索结果';
descEl.textContent = '未找到匹配内容';
container.innerHTML = '<div class="empty-docs"><div class="empty-docs-icon">🔍</div><p class="empty-docs-text">未找到匹配的分类或文档</p></div>';
return;
}
let targetCat = null;
let targetItems = [];
if (activeSubId) {
const result = findNode(activeSubId);
if (result && result.node) {
targetCat = result.node;
targetItems = targetCat.items || [];
}
} else if (activeId) {
const result = findNode(activeId);
if (result && result.node) {
targetCat = result.node;
targetItems = targetCat.items || [];
}
}
if (!targetCat) {
titleEl.textContent = '选择分类';
descEl.textContent = '从左侧选择分类查看文档';
container.innerHTML = '<div class="empty-docs"><div class="empty-docs-icon">📋</div><p class="empty-docs-text">请从左侧选择分类</p></div>';
return;
}
titleEl.textContent = targetCat.name;
descEl.textContent = targetItems.length + ' 篇文档';
const isTimelineCategory = (activeId === 1 || activeSubId === 1);
if (isTimelineCategory) {
let allItems = [...(targetCat.items || [])];
if (targetCat.children && targetCat.children.length > 0) {
targetCat.children.forEach(function(child) {
if (child.items && child.items.length > 0) {
allItems = allItems.concat(child.items);
}
});
}
descEl.textContent = allItems.length + ' 个时间节点';
renderTimeline(allItems, container);
return;
}
if (targetItems.length === 0) {
container.innerHTML = '<div class="empty-docs"><div class="empty-docs-icon">📭</div><p class="empty-docs-text">该分类暂无文档</p></div>';
return;
}
container.innerHTML = targetItems.map(function(item) {
return '<a href="' + item.wiki + '" target="_blank" class="doc-item">' +
'<div class="doc-header">' +
'<h3 class="doc-title">' + item.title + '</h3>' +
'<div class="doc-actions">' +
'<button class="doc-action-btn" onclick="openEditDocModal(event, ' + item.id + ', ' + item.index + ')" title="编辑">✏️</button>' +
'<button class="doc-action-btn" onclick="deleteDoc(event, ' + item.id + ')" title="删除">🗑️</button>' +
'</div>' +
'</div>' +
'<div class="doc-content">' +
'<p class="doc-desc">' + item.desc + '</p>' +
'</div>' +
'</a>';
}).join('');
}
function selectCategory(id, level) {
if (level === 0) {
activeId = id;
activeSubId = null;
} else {
activeSubId = id;
}
renderCategories();
renderDocuments();
const addBtn = document.getElementById('btn-add-doc');
if (addBtn) {
addBtn.style.display = 'inline-flex';
}
}
function toggleExpand(event, id, level) {
event.stopPropagation();
const result = findNode(id);
if (result && result.node) {
result.node.expanded = !result.node.expanded;
saveData();
renderCategories();
}
}
function searchDocs() {
const input = document.getElementById("searchInput");
searchQuery = input.value.trim();
if (!searchQuery) {
searchResults = { categories: [], items: [], sourceCategoryId: null };
renderDocuments();
return;
}
const query = searchQuery.toLowerCase();
const matchedCategories = [];
const matchedItems = [];
function searchInCategories(categories) {
categories.forEach(function(cat) {
if (cat.name && cat.name.toLowerCase().indexOf(query) !== -1) {
matchedCategories.push(cat);
}
if (cat.items && cat.items.length > 0) {
cat.items.forEach(function(item, index) {
if (item.title && item.title.toLowerCase().indexOf(query) !== -1 ||
(item.desc && item.desc.toLowerCase().indexOf(query) !== -1)) {
if (!matchedItems.find(function(mi) { return mi.id === item.id; })) {
matchedItems.push({
...item,
categoryName: cat.name,
categoryId: cat.id,
index: index
});
}
}
});
}
if (cat.children && cat.children.length > 0) {
searchInCategories(cat.children);
}
});
}
searchInCategories(data);
searchResults = {
categories: matchedCategories,
items: matchedItems,
sourceCategoryId: null
};
renderDocuments();
}
function renderTimeline(items, container) {
if (items.length === 0) {
container.innerHTML = '<div class="empty-docs"><div class="empty-docs-icon">📅</div><p class="empty-docs-text">暂无时间节点</p></div>';
return;
}
container.innerHTML = '<div class="timeline">' + items.map(function(item, index) {
return '<div class="timeline-item">' +
'<div class="timeline-dot">' + (index + 1) + '</div>' +
'<div class="timeline-content">' +
'<div class="timeline-header">' +
'<div class="timeline-title">' + item.title + '</div>' +
'<div class="timeline-actions">' +
'<button class="timeline-action-btn" onclick="openEditDocModal(event, ' + item.id + ', ' + index + ')" title="编辑">✏️</button>' +
'<button class="timeline-action-btn" onclick="deleteDoc(event, ' + item.id + ')" title="删除">🗑️</button>' +
'</div>' +
'</div>' +
'<p class="timeline-desc">' + item.desc + '</p>' +
'</div>' +
'</div>';
}).join('') + '</div>';
}
let currentModalMode = 'doc-add';
let currentEditId = null;
function openAddDocModal() {
const categoryId = activeSubId || activeId;
currentModalMode = 'doc-add';
currentEditId = null;
document.getElementById('doc-modal-title').textContent = '添加文档';
document.getElementById('edit-doc-id').value = '';
document.getElementById('doc-category-id').value = categoryId || '';
document.getElementById('doc-title').value = '';
document.getElementById('doc-desc').value = '';
document.getElementById('doc-wiki').value = '';
showField('title-field');
showField('desc-field');
showField('wiki-field');
document.getElementById('doc-modal').style.display = 'flex';
}
function openEditDocModal(event, docId, docIndex) {
event.stopPropagation();
event.preventDefault();
const result = findNode(activeSubId || activeId);
if (!result || !result.node) {
alert('未找到分类');
return;
}
const items = result.node.items || [];
const doc = items[docIndex];
if (!doc) {
alert('未找到文档');
return;
}
currentModalMode = 'doc-edit';
currentEditId = docId;
document.getElementById('doc-modal-title').textContent = '编辑文档';
document.getElementById('edit-doc-id').value = docId;
document.getElementById('doc-category-id').value = result.node.id;
document.getElementById('doc-title').value = doc.title;
document.getElementById('doc-desc').value = doc.desc;
document.getElementById('doc-wiki').value = doc.wiki;
showField('title-field');
showField('desc-field');
showField('wiki-field');
document.getElementById('doc-modal').style.display = 'flex';
}
function deleteDoc(event, docId) {
event.stopPropagation();
event.preventDefault();
const result = findNode(activeSubId || activeId);
if (!result || !result.node) {
alert('未找到分类');
return;
}
if (confirm('确定要删除这个文档吗?此操作不可恢复。')) {
const items = result.node.items || [];
const index = items.findIndex(function(item) { return item.id === docId; });
if (index !== -1) {
items.splice(index, 1);
saveData();
renderDocuments();
}
}
}
function openAddCategoryModal(parentId, level) {
currentModalMode = 'category-add';
currentEditId = null;
document.getElementById('doc-modal-title').textContent = level > 0 ? '新增子类' : '新增主类';
document.getElementById('edit-doc-id').value = '';
document.getElementById('doc-category-id').value = parentId || '';
document.getElementById('doc-title').value = '';
document.getElementById('doc-desc').value = '';
document.getElementById('doc-wiki').value = '';
showField('title-field');
hideField('desc-field');
hideField('wiki-field');
document.getElementById('doc-modal').style.display = 'flex';
}
function openEditCategoryModal(id, level) {
const result = findNode(id);
if (!result || !result.node) {
alert('未找到分类');
return;
}
currentModalMode = 'category-edit';
currentEditId = id;
document.getElementById('doc-modal-title').textContent = level > 0 ? '编辑子类' : '编辑主类';
document.getElementById('edit-doc-id').value = id;
document.getElementById('doc-category-id').value = result.node.parent ? result.node.parent.id : '';
document.getElementById('doc-title').value = result.node.name;
document.getElementById('doc-desc').value = result.node.icon || '📁';
showField('title-field');
showField('desc-field');
hideField('wiki-field');
document.getElementById('doc-modal').style.display = 'flex';
}
function deleteCategory(id, level) {
if (!confirm('确定要删除这个分类吗?此操作不可恢复。')) {
return;
}
const result = findNode(id);
if (!result || !result.node) {
alert('未找到分类');
return;
}
if (result.type === 'main') {
data.splice(result.index, 1);
if (activeId === id) {
activeId = null;
activeSubId = null;
}
} else {
if (result.parent && result.parent.children) {
result.parent.children.splice(result.index, 1);
if (activeSubId === id) {
activeSubId = null;
}
}
}
saveData();
renderCategories();
renderDocuments();
}
function saveFromModal() {
const categoryId = document.getElementById('doc-category-id').value;
const title = document.getElementById('doc-title').value.trim();
const desc = document.getElementById('doc-desc').value.trim();
const wiki = document.getElementById('doc-wiki').value.trim();
if (!title) {
alert('请输入标题');
return;
}
if (currentModalMode === 'doc-add' || currentModalMode === 'doc-edit') {
if (!desc) {
alert('请输入描述');
return;
}
if (!wiki) {
alert('请输入 Wiki 链接');
return;
}
const result = findNode(parseInt(categoryId));
if (!result || !result.node) {
alert('未找到分类');
return;
}
const items = result.node.items || [];
if (currentModalMode === 'doc-edit') {
const index = items.findIndex(function(item) { return item.id === parseInt(currentEditId); });
if (index !== -1) {
items[index].title = title;
items[index].desc = desc;
items[index].wiki = wiki;
}
} else {
const maxId = items.reduce(function(max, item) {
const id = item.id ? item.id : 0;
return id > max ? id : max;
}, 0);
items.push({
id: maxId + 1,
title: title,
desc: desc,
wiki: wiki
});
}
saveData();
renderDocuments();
closeModal();
return;
}
if (currentModalMode === 'category-add' || currentModalMode === 'category-edit') {
const name = title;
const icon = currentModalMode === 'category-add' ? '📁' : (desc || '📁');
if (currentModalMode === 'category-edit') {
const result = findNode(parseInt(currentEditId));
if (result && result.node) {
result.node.name = name;
result.node.icon = icon;
}
} else {
if (categoryId) {
const parentResult = findNode(parseInt(categoryId));
if (parentResult && parentResult.node) {
if (!parentResult.node.children) {
parentResult.node.children = [];
}
const maxId = parentResult.node.children.reduce(function(max, c) { return c.id > max ? c.id : max; }, 100);
parentResult.node.children.push({
id: maxId + 1,
name: name,
icon: icon,
items: [],
children: [],
expanded: false
});
parentResult.node.expanded = true;
}
} else {
const maxId = data.reduce(function(max, c) { return c.id > max ? c.id : max; }, 0);
data.push({
id: maxId + 1,
name: name,
icon: icon,
items: [],
children: [],
expanded: false
});
}
}
saveData();
renderCategories();
closeModal();
}
}
function closeModal() {
document.getElementById('doc-modal').style.display = 'none';
}
function showField(fieldId) {
const field = document.getElementById(fieldId);
if (field) field.style.display = 'block';
}
function hideField(fieldId) {
const field = document.getElementById(fieldId);
if (field) field.style.display = 'none';
}
document.addEventListener("DOMContentLoaded", function() {
console.log('[Learning] Page loaded, initializing...');
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('reset') === 'true') {
console.log('[Learning] Reset flag detected, clearing all data...');
localStorage.removeItem('learningCategories');
localStorage.removeItem('operator_efficiency_learningCategories');
alert('数据已重置,页面将重新加载');
window.location.href = window.location.pathname;
return;
}
loadData();
renderCategories();
renderDocuments();
console.log('[Learning] Initialization complete');
document.getElementById('doc-modal-close').addEventListener('click', closeModal);
document.getElementById('doc-modal-cancel').addEventListener('click', closeModal);
document.getElementById('doc-modal-save').addEventListener('click', saveFromModal);
document.getElementById('add-category-btn').addEventListener('click', function() {
openAddCategoryModal(null, 0);
});
document.getElementById('doc-modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
});
</script>
</body>
</html>