优化html目录生成效率

This commit is contained in:
xunbu
2026-01-11 19:31:50 +08:00
parent d04fb90314
commit 96e9404a76
3 changed files with 114 additions and 47 deletions

View File

@@ -254,20 +254,42 @@ async def lifespan(app: FastAPI):
global_logger.propagate = False
global_logger.setLevel(logging.INFO)
print("应用启动完成,多任务状态已初始化。")
print(f"服务接口文档: http://127.0.0.1:{app.state.port_to_use}/docs")
print(f"请用浏览器访问 http://127.0.0.1:{app.state.port_to_use}\n")
yield
# 清理任何可能残留的临时目录
if hasattr(app.state, "port_to_use"):
print(f"服务接口文档: http://127.0.0.1:{app.state.port_to_use}/docs")
print(f"请用浏览器访问 http://127.0.0.1:{app.state.port_to_use}\n")
yield # 应用运行中...
# --- 关闭阶段 ---
print("正在关闭应用,开始清理资源...")
# 1. 优先取消所有正在进行的后台任务
# 如果不取消uvicorn 会等待它们完成,导致进程挂起
pending_tasks = []
for task_id, task_state in tasks_state.items():
task_ref = task_state.get("current_task_ref")
if task_ref and not task_ref.done():
print(f"[{task_id}] 检测到未完成任务,正在强制取消...")
task_ref.cancel()
pending_tasks.append(task_ref)
# 等待所有任务完成取消操作 (捕获 CancelledError 避免报错)
if pending_tasks:
await asyncio.gather(*pending_tasks, return_exceptions=True)
# 2. 关闭 HTTP 客户端连接
await httpx_client.aclose()
# 3. 清理所有任务的临时目录
for task_id, task_state in tasks_state.items():
temp_dir = task_state.get("temp_dir")
if temp_dir and os.path.isdir(temp_dir):
try:
shutil.rmtree(temp_dir)
print(f"应用关闭,清理任务 '{task_id}' 的临时目录: {temp_dir}")
# ignore_errors=True 防止 Windows 上因文件被短暂占用导致的删除失败报错
shutil.rmtree(temp_dir, ignore_errors=True)
print(f"[{task_id}] 临时目录已清理: {temp_dir}")
except Exception as e:
print(f"清理任务 '{task_id}'临时目录 '{temp_dir}' 时出错: {e}")
await httpx_client.aclose()
print("应用关闭,资源已清理。")
print(f"[{task_id}] 清理临时目录 '{temp_dir}' 时出错: {e}")
print("应用关闭,资源已彻底释放。")
# --- FastAPI 应用和路由设置 ---

View File

@@ -63,6 +63,7 @@
overflow-y: auto;
transform: translateX(-100%);
transition: transform 0.2s ease;
will-change: transform; /* 性能优化提示 */
}
#toc-panel.show { transform: translateX(0); }
@@ -77,10 +78,9 @@
font-size: 13px;
border-radius: 3px;
cursor: pointer;
user-select: none; /* 防止频繁点击时选中文本 */
}
.toc-link:hover { background: #f5f5f5; }
.toc-link.active { background: #007bff; color: #fff; }
.toc-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.toc-expander {
width: 18px;
@@ -92,12 +92,12 @@
cursor: pointer;
flex-shrink: 0;
}
.toc-expander:hover { color: #333; }
.toc-expander:hover { color: #333; background: rgba(0,0,0,0.05); border-radius: 2px; }
.toc-children { display: none; padding-left: 12px; }
.toc-children.expanded { display: block; }
.toc-item[data-level="1"] > .toc-link { font-weight: 500; }
.toc-item[data-level="1"] > .toc-link { font-weight: 500; color: #333; }
.toc-item[data-level="2"] > .toc-link { padding-left: 20px; }
.toc-item[data-level="3"] > .toc-link { padding-left: 30px; }
.toc-item[data-level="4"] > .toc-link { padding-left: 40px; }
@@ -115,81 +115,125 @@
(function() {
const panel = document.getElementById('toc-panel');
const list = document.getElementById('toc-list');
let headings = [];
function init() {
headings = Array.from(document.querySelectorAll('main h1, main h2, main h3, main h4, main h5, main h6'));
// UI交互逻辑
window.toggleToc = function() { panel.classList.toggle('show'); };
// 事件委托:处理目录点击与折叠
list.addEventListener('click', function(e) {
// 1. 处理折叠/展开 (+/- 按钮)
if (e.target.classList.contains('toc-expander')) {
e.stopPropagation();
const ul = e.target.closest('li').querySelector('.toc-children');
if (ul) {
ul.classList.toggle('expanded');
e.target.textContent = ul.classList.contains('expanded') ? '-' : '+';
}
return;
}
// 2. 处理点击跳转
const link = e.target.closest('.toc-link');
if (link && link.dataset.target) {
const targetEl = document.getElementById(link.dataset.target);
if (targetEl) {
targetEl.scrollIntoView({ behavior: 'smooth' });
// 在移动端点击后自动关闭面板
if (window.innerWidth < 768) panel.classList.remove('show');
}
}
});
// 核心生成逻辑
function buildToc() {
const headings = Array.from(document.querySelectorAll('main h1, main h2, main h3, main h4, main h5, main h6'));
if (headings.length === 0) return;
// 确保所有标题都有ID
headings.forEach((h, i) => { if (!h.id) h.id = 'h-' + i; });
// 构建树状结构
const root = { level: 0, children: [] };
const stack = [root];
headings.forEach(h => {
const level = parseInt(h.tagName[1]);
const item = { id: h.id, text: h.textContent.trim(), level, children: [] };
while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
stack.pop();
}
stack[stack.length - 1].children.push(item);
stack.push(item);
});
render(root.children, list);
// 使用 DocumentFragment 进行离线 DOM 构建 (性能关键点)
const fragment = document.createDocumentFragment();
renderRecursive(root.children, fragment);
list.appendChild(fragment);
}
function render(items, parent) {
// 递归渲染函数
function renderRecursive(items, container) {
items.forEach(item => {
const li = document.createElement('li');
li.className = 'toc-item';
li.dataset.level = item.level;
// 链接部分
const div = document.createElement('div');
div.className = 'toc-link';
div.innerHTML = `<span class="toc-text">${item.text}</span>`;
div.dataset.target = item.id; // 存储目标ID供事件委托使用
const textSpan = document.createElement('span');
textSpan.className = 'toc-text';
textSpan.textContent = item.text;
div.appendChild(textSpan);
// 折叠按钮
let expander = null;
if (item.children.length) {
const expander = document.createElement('span');
expander = document.createElement('span');
expander.className = 'toc-expander';
expander.textContent = '+';
expander.onclick = function(e) { e.stopPropagation(); toggle(this); };
// 默认展开一级菜单,其他折叠
const isRoot = item.level === 1;
expander.textContent = isRoot ? '-' : '+';
div.appendChild(expander);
}
div.onclick = function() { scrollTo(item.id); };
li.appendChild(div);
// 子菜单
if (item.children.length) {
const ul = document.createElement('ul');
ul.className = 'toc-children';
render(item.children, ul);
if (item.level === 1) ul.classList.add('expanded');
renderRecursive(item.children, ul);
li.appendChild(ul);
if (item.level === 1) {
ul.classList.add('expanded');
expander.textContent = '-';
}
}
parent.appendChild(li);
container.appendChild(li);
});
}
function toggle(btn) {
const ul = btn.closest('li').querySelector('.toc-children');
ul.classList.toggle('expanded');
btn.textContent = ul.classList.contains('expanded') ? '-' : '+';
}
// 调度执行:优先显示内容,空闲时生成目录
const idleCallback = window.requestIdleCallback || function(cb) { setTimeout(cb, 10); };
idleCallback(buildToc);
function scrollTo(id) {
const el = document.getElementById(id);
if (el) el.scrollIntoView({ behavior: 'smooth' });
}
window.toggleToc = function() { panel.classList.toggle('show'); };
init();
})();
// 辅助脚本
setTimeout(() => {
for (const e of document.getElementsByClassName('katex-error')) e.innerHTML = e.title;
// 修复 Katex 错误显示
const errors = document.getElementsByClassName('katex-error');
for (let i = 0; i < errors.length; i++) {
errors[i].innerHTML = errors[i].title;
}
}, 200);
mermaid.initialize({ securityLevel: 'loose', startOnLoad: true });
// 确保 Mermaid 存在再初始化
if (typeof mermaid !== 'undefined') {
mermaid.initialize({ securityLevel: 'loose', startOnLoad: true });
}
</script>
</html>
</html>

View File

@@ -7,6 +7,7 @@ v1.6.2版 2025.1.11
- 自动生成术语表不覆盖用户术语表,最终下载的是合并术语表
优化
- 移除tiktoken依赖
- 优化html目录生成效率
- 其它优化
----------------
v1.6.0版 2025.12.31