refactor(web): 营销站重构为 Eleventy SSG,内容迁移至 Markdown
- 引入 Eleventy 静态站点生成器,页面统一使用 Nunjucks 模板 - base.njk 提供公共 nav / footer / CSS,消除三个页面间的重复代码 - 抽取 color.css(设计 token)+ style.css(公共组件 + utility 类) - index.njk:首页迁移,用 utility class 替换大量页面私有 CSS - download.njk:下载页重构,版本号/更新日志从 CHANGELOG.md 构建时解析,OS 自动识别 - docs.njk:文档页重构,12 章内容从 web/content/docs.md 构建时渲染,三列布局保留 - docs.css 单独提取,docs 专有样式不污染公共 CSS - 内容面向非技术用户重写,去除技术术语和内部路径引用 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+194
@@ -0,0 +1,194 @@
|
||||
---
|
||||
layout: base.njk
|
||||
title: 使用手册
|
||||
description: 岩美酒库管理系统使用手册,覆盖入库、出库、库存、财务等全功能操作说明。
|
||||
pageExtraCss: /assets/docs.css
|
||||
---
|
||||
|
||||
<!-- ============================== DOCS SUB-NAV ============================== -->
|
||||
<div class="docs-subnav">
|
||||
<div class="docs-subnav-inner">
|
||||
<nav class="breadcrumb">
|
||||
<a href="/">岩美</a>
|
||||
<span class="sep">/</span>
|
||||
<span class="current">使用手册</span>
|
||||
</nav>
|
||||
|
||||
<div class="docs-search">
|
||||
<i data-lucide="search" class="search-icon icon"></i>
|
||||
<input type="text" placeholder="搜索文档..." id="docs-search-input" />
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
|
||||
<div class="docs-version">
|
||||
<i data-lucide="tag" class="icon"></i>
|
||||
<span class="docs-version-label">版本</span>
|
||||
<span>v{{ changelog[0].version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================== THREE-COLUMN LAYOUT ============================== -->
|
||||
<div class="docs-layout">
|
||||
|
||||
<!-- Left TOC -->
|
||||
<aside class="docs-toc" id="docs-toc">
|
||||
{% for group in docs.toc %}
|
||||
<div class="toc-section">
|
||||
<div class="toc-section-title">{{ group.group }}</div>
|
||||
{% for item in group.items %}
|
||||
<a href="#{{ item.id }}" class="toc-link" data-chapter="{{ item.id }}">
|
||||
<span class="toc-num">{{ item.num }}</span>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="docs-main" id="docs-main">
|
||||
{% for chapter in docs.chapters %}
|
||||
<section class="docs-chapter" id="{{ chapter.id }}">
|
||||
<h2>{{ chapter.num }}. {{ chapter.title }}</h2>
|
||||
{{ chapter.html | safe }}
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
||||
<div class="docs-feedback">
|
||||
<span class="feedback-q">这篇文档对您有帮助吗?</span>
|
||||
<div class="feedback-actions">
|
||||
<button class="feedback-btn" onclick="sendFeedback('yes')">
|
||||
<i data-lucide="thumbs-up" class="icon"></i>有帮助
|
||||
</button>
|
||||
<button class="feedback-btn" onclick="sendFeedback('no')">
|
||||
<i data-lucide="thumbs-down" class="icon"></i>需要改进
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Right rail -->
|
||||
<aside class="docs-rail" id="docs-rail">
|
||||
<div class="rail-title">本页目录</div>
|
||||
<ul class="rail-list" id="rail-list"></ul>
|
||||
|
||||
<div class="rail-actions">
|
||||
<button class="rail-action" onclick="window.print()">
|
||||
<i data-lucide="printer" class="icon"></i>打印此页
|
||||
</button>
|
||||
<button class="rail-action" id="copy-link-btn">
|
||||
<i data-lucide="link" class="icon"></i>复制链接
|
||||
</button>
|
||||
<a class="rail-action" href="#ch-1">
|
||||
<i data-lucide="arrow-up" class="icon"></i>回到顶部
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var chapters = document.querySelectorAll('.docs-chapter');
|
||||
var tocLinks = document.querySelectorAll('.toc-link');
|
||||
var railList = document.getElementById('rail-list');
|
||||
|
||||
// Build right rail from all chapter h2+h3 headings
|
||||
function buildRail() {
|
||||
var items = [];
|
||||
chapters.forEach(function(ch) {
|
||||
var chId = ch.id;
|
||||
var chTitle = ch.querySelector('h2');
|
||||
if (chTitle) {
|
||||
items.push({ id: chId, text: chTitle.textContent, level: 2 });
|
||||
}
|
||||
ch.querySelectorAll('h3').forEach(function(h3) {
|
||||
if (!h3.id) {
|
||||
h3.id = chId + '-' + h3.textContent.replace(/\s+/g, '-').replace(/[^\w\-]/g, '').toLowerCase();
|
||||
}
|
||||
items.push({ id: h3.id, text: h3.textContent, level: 3 });
|
||||
});
|
||||
});
|
||||
railList.innerHTML = items.map(function(item) {
|
||||
var indent = item.level === 3 ? ' style="padding-left:20px;"' : '';
|
||||
return '<li><a href="#' + item.id + '"' + indent + '>' + item.text + '</a></li>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Scrollspy: highlight active TOC link and rail link
|
||||
function setupScrollspy() {
|
||||
if (!window.IntersectionObserver) return;
|
||||
var activeChapterId = null;
|
||||
|
||||
var observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
activeChapterId = entry.target.id;
|
||||
}
|
||||
});
|
||||
if (!activeChapterId) return;
|
||||
|
||||
// Update left TOC
|
||||
tocLinks.forEach(function(link) {
|
||||
var isActive = link.getAttribute('data-chapter') === activeChapterId;
|
||||
link.classList.toggle('active', isActive);
|
||||
});
|
||||
|
||||
// Update rail links
|
||||
railList.querySelectorAll('a').forEach(function(a) {
|
||||
a.classList.remove('active');
|
||||
if (a.getAttribute('href') === '#' + activeChapterId) {
|
||||
a.classList.add('active');
|
||||
}
|
||||
});
|
||||
}, { rootMargin: '-20% 0px -70% 0px', threshold: 0 });
|
||||
|
||||
chapters.forEach(function(ch) { observer.observe(ch); });
|
||||
}
|
||||
|
||||
// Simple search: highlight matching text in main content
|
||||
var searchInput = document.getElementById('docs-search-input');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
var q = this.value.trim().toLowerCase();
|
||||
if (!q) {
|
||||
chapters.forEach(function(ch) { ch.style.display = ''; });
|
||||
return;
|
||||
}
|
||||
chapters.forEach(function(ch) {
|
||||
var text = ch.textContent.toLowerCase();
|
||||
ch.style.display = text.includes(q) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Copy link button
|
||||
var copyBtn = document.getElementById('copy-link-btn');
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', function() {
|
||||
navigator.clipboard.writeText(window.location.href).then(function() {
|
||||
copyBtn.querySelector('span, .icon') && null;
|
||||
var orig = copyBtn.innerHTML;
|
||||
copyBtn.innerHTML = '<i data-lucide="check" class="icon"></i>已复制';
|
||||
if (window.lucide) lucide.createIcons();
|
||||
setTimeout(function() { copyBtn.innerHTML = orig; if (window.lucide) lucide.createIcons(); }, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendFeedback(val) {
|
||||
// Fire-and-forget; no user-visible side effect needed
|
||||
try { fetch('/api/v1/public/feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ helpful: val === 'yes' }) }); } catch(e) {}
|
||||
var fb = document.querySelector('.docs-feedback');
|
||||
if (fb) fb.innerHTML = '<span style="color:var(--success-700);font-size:var(--text-md);">感谢您的反馈!</span>';
|
||||
}
|
||||
window.sendFeedback = sendFeedback;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
buildRail();
|
||||
setupScrollspy();
|
||||
if (window.lucide) lucide.createIcons();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user