
Knowledge Site Creator
- 832 installs
- 361 repo stars
- Updated February 25, 2026
- joeseesun/qiaomu-knowledge-site-creator
knowledge-site-creator is a generator skill that turns notes, wikis, or markdown collections into searchable public knowledge sites with flashcards, quizzes, and Vercel deployment for developers who publish structured le
About
knowledge-site-creator is a generator skill listed on skills.sh with 91 installs at rank 67 for developers who want a clean, searchable public knowledge site from notes, wikis, or markdown files. The agent analyzes a topic, authors 20–30 core concepts with descriptions and quiz questions, emits a siteConfig.js with hero copy and stats, and scaffolds pages using a bundled design system. It implements four learning modes—flashcards with keyboard shortcuts, progressive learn pages, knowledge quizzes, and a searchable concept index—plus PWA setup guided by five reference documents covering core patterns, design system, code quality, PWA config, and SEO. Deployment targets Vercel with enforced safety checks. Reach for knowledge-site-creator when turning scattered markdown or wiki exports into a deployed learning site instead of hand-building every page.
- Converts markdown, Notion exports, or text files into a ready-to-publish knowledge base
- Generates navigation, search, and responsive static site in one command
- Supports custom themes and structured metadata for SEO-friendly docs
- Outputs a complete static site deployable to Vercel, Netlify or GitHub Pages
Knowledge Site Creator by the numbers
- 832 all-time installs (skills.sh)
- Ranked #297 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joeseesun/qiaomu-knowledge-site-creator --skill knowledge-site-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 832 |
|---|---|
| repo stars | ★ 361 |
| Last updated | February 25, 2026 |
| Repository | joeseesun/qiaomu-knowledge-site-creator ↗ |
How do you turn markdown notes into a searchable knowledge site?
Instantly turn any collection of notes, wikis, or markdown files into a clean, searchable, public knowledge site.
Who is it for?
Developers with markdown notes or wiki exports who want a deployed, searchable learning site with flashcards and quizzes without hand-coding every page.
Skip if: Enterprise documentation platforms like Docusaurus migrations or apps needing custom backend auth beyond static deployment.
When should I use this skill?
The user wants to publish notes, wikis, or markdown as a searchable knowledge site with flashcards, quizzes, and one-click Vercel deploy.
What you get
A deployed Vercel knowledge site with siteConfig.js, concept pages, flashcards, quizzes, PWA manifest, and searchable index.
- siteConfig.js
- Deployed Vercel knowledge site
- Flashcard and quiz pages
By the numbers
- 91 installs at rank 67 on skills.sh
- Generates 20–30 core concepts with 4 learning modes
- Bundles 5 reference guides for design system, PWA, SEO, patterns, and code quality
Files
Knowledge Site Creator - 通用知识学习网站生成器
AI理解主题,自动创作内容,生成网站,一键部署。
核心理念
设计系统优先:
- 复用设计语言(极简主义、配色、布局、交互模式)
- 不复用具体页面代码
- AI根据主题重新创作所有内容
通用学习模式(核心功能):
- 闪卡(Flashcard) - 快速记忆
- 学习(Learn) - 渐进式学习
- 测试(Quiz) - 知识检验
- 索引(Index) - 快速查找
- 进度(Progress) - 学习追踪
零模板依赖:
- 不再
cp -r复制模板 - AI参考设计系统,生成新页面
- 所有文案、统计、介绍都由AI创作
触发方式
- "生成一个XXX学习网站"
- "创建XXX知识网站"
- "做个XXX学习工坊"
示例:
- "生成一个进化心理学概念学习网站"
- "创建量子力学基础概念网站"
- "做个中医经络穴位学习工坊"
工作流程
用户视角(一句话)
用户:"生成一个进化心理学学习网站"
AI自动执行:
✓ 分析"进化心理学"特点和价值
✓ 生成30个核心概念数据
✓ 创作首页文案、统计、介绍
✓ 参考设计系统生成页面
✓ 部署到 Vercel
✓ 返回:https://evolutionary-psychology.vercel.app
完成!---
实施流程(AI执行)
Step 1: 理解主题
AI深入分析主题,输出主题分析:
主题分析 {
领域: "进化心理学",
特点: "跨学科(生物学+心理学),解释人类行为的底层逻辑",
价值: "理解人性、改善关系、优化决策",
受众: "心理学爱好者、自我提升者、教育工作者",
表达: "科学严谨 + 生活化案例,避免学术术语堆砌"
}思考问题:
- 这是什么领域?(学科分类、知识特点)
- 为什么重要?(学习价值、应用场景)
- 目标受众是谁?(背景、需求、痛点)
- 如何表达更好?(语言风格、案例选择)
---
Step 2: 生成数据 + 网站配置
⚠️ 关键:生成两个文件,不只是数据!
2.1 生成数据(wordData.js)
通用数据结构:
const WordRoots = [
{
id: 1,
root: "适应性 (Adaptation)", // 知识点名称
origin: "核心理论", // 分类/来源
meaning: "通过自然选择进化出的有利特征", // 一句话解释
description: "详细说明(200-300字)...",
examples: [ // 应用案例/例子(3个)
{
word: "恐高症",
meaning: "对高处的恐惧",
breakdown: { root: "适应性" },
explanation: "详细解释..."
}
],
quiz: { // 小测试(4选1)
question: "以下哪个不是适应性的特征?",
options: ["选项A", "选项B", "选项C", "选项D"],
correctAnswer: 2 // 正确答案索引(0-3)
}
}
];生成数量:默认20-30个,根据主题复杂度调整
2.2 生成配置(siteConfig.js)🆕
AI创作,完全适配主题:
const siteConfig = {
// 基础信息
topic: "进化心理学",
siteName: "进化心理学概念工坊",
itemName: "概念", // 单个知识点的称呼
itemCount: 30,
// 首页Hero区(AI创作)
hero: {
title: [
"30个核心概念",
"理解人类行为",
"的底层逻辑"
],
subtitle: "从适应性到配偶选择,系统掌握进化心理学核心框架",
animation: {
enabled: true, // 是否显示动画
demoCount: 5 // 动画展示几个概念
}
},
// 统计卡片(AI生成,匹配主题特点)
stats: [
{ value: "30", label: "核心概念" },
{ value: "100+", label: "生活应用" },
{ value: "15分钟", label: "每日学习" }
],
// 底部介绍(AI创作)
footer: {
tagline: "像理解自己一样理解人性",
description: "基于进化心理学的科学框架,用30个核心概念解释人类行为背后的生物学逻辑。从配偶选择到亲子关系,从群体合作到情绪反应,让你看懂人性的深层原因。"
},
// 按钮文案(AI适配)
cta: {
primary: "开始第一个概念 →",
secondary: "闪卡复习"
}
};AI创作原则:
hero.title: 简洁有力,3行,突出核心价值hero.subtitle: 具体说明学什么,为什么学stats: 真实、有说服力的数字,匹配主题特点footer.tagline: 一句话点题,朗朗上口footer.description: 2-3句,说清楚是什么、学什么、有什么用
---
Step 3: 参考设计系统,生成页面
⚠️ 不再复制模板!AI参考设计规范,生成新页面
3.1 设计系统参考
⚠️ 参考文档:references/design-system.md - 完整的设计规范
核心要点:
- 配色:黄色主题色 (#FBBF24),灰色系文字和背景
- 字体:Inter字体族,代码用Courier New
- 风格:极简主义,大留白,清晰层级
- 组件:圆角卡片(12px),极浅阴影
- 间距:8px网格系统,Hero区96px留白
详细配色、字体、间距、组件样式见 design-system.md
3.2 生成页面清单
⚠️ 功能参考:
references/core-patterns.md- 核心学习模式实现references/code-quality.md- 代码质量标准(必须遵守)references/seo-best-practices.md- SEO优化指南 🆕references/pwa-setup.md- PWA配置指南
代码质量要求(强制):
- ✅ 错误处理:所有 LocalStorage 操作必须有 try-catch
- ✅ XSS 防护:使用 textContent/createElement,禁止直接 innerHTML 插入未转义数据
- ✅ DOM 安全:所有 DOM 操作前检查元素存在
- ✅ 避免全局污染:使用模块封装或 IIFE
详细规则见 references/code-quality.md。
AI参考设计系统,从零生成以下页面:
1. index.html - 首页 🆕
- Hero区:使用
siteConfig.hero.title/subtitle - 动画演示:从
WordRoots动态加载前5个(见core-patterns.md §9) - 统计卡片:使用
siteConfig.stats - CTA按钮:使用
siteConfig.cta - Footer:使用
siteConfig.footer
2. learn.html - 学习页(见core-patterns.md §5)
- 渐进式卡片展示
- 上一个/下一个导航
- 标记已掌握功能
3. flashcard.html - 闪卡页(见core-patterns.md §4)
- 卡片翻转动画
- 键盘快捷键(←→翻页,空格翻转)
- 进度显示
4. roots.html - 索引页(见core-patterns.md §7)
- 标题适配:
${itemName}索引 - 搜索框 + 筛选器
- 卡片网格布局
5. progress.html - 进度页(见core-patterns.md §8)
- 学习统计
- 已掌握列表
- 成就系统
6. root-detail.html - 详情页
- 概念详细说明
- 例子展示
- 测试题(见core-patterns.md §6)
7. css/minimal.css - 样式文件(见design-system.md)
- 统一设计系统
- 响应式布局
8. js/storage.js - 存储逻辑(见core-patterns.md §3)
- LocalStorage 进度管理
9. manifest.json - PWA 配置(见pwa-setup.md §1)🆕
- App 名称、图标、主题色
- 支持安装到主屏幕
10. sw.js - Service Worker(见pwa-setup.md §2)🆕
- 缓存静态资源
- 支持离线访问
11. icon-192.png / icon-512.png - PWA 图标 🆕
- 自动生成:使用 PIL 从配置生成(黄色背景 + 主题文字)
- 不要手动创建:AI 应自动用 Python PIL 生成
12. sitemap.xml - 网站地图(见seo-best-practices.md §4)🆕
- 列出所有页面URL
- 提交到搜索引擎
13. robots.txt - 爬虫指令(见seo-best-practices.md §5)🆕
- 允许/禁止抓取规则
- Sitemap 位置声明
⚠️ 强制要求:所有 HTML 文件必须包含完整的 meta 标签
每个 HTML 文件的 <head> 必须包含:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${siteConfig.siteName}</title>
<!-- SEO 基础 -->
<meta name="description" content="${siteConfig.footer.description}">
<meta name="keywords" content="${siteConfig.topic},学习,知识,${siteConfig.itemName}">
<meta name="author" content="乔木">
<meta name="language" content="zh-CN">
<meta name="robots" content="index, follow">
<link rel="canonical" href="${currentPageUrl}">
<!-- Open Graph (社交分享) -->
<meta property="og:title" content="${siteConfig.siteName}">
<meta property="og:description" content="${siteConfig.footer.description}">
<meta property="og:type" content="website">
<meta property="og:url" content="${currentPageUrl}">
<meta property="og:image" content="${siteBaseUrl}/icon-512.png">
<meta property="og:site_name" content="${siteConfig.siteName}">
<meta property="og:locale" content="zh_CN">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@vista8">
<meta name="twitter:creator" content="@vista8">
<meta name="twitter:title" content="${siteConfig.siteName}">
<meta name="twitter:description" content="${siteConfig.footer.description}">
<meta name="twitter:image" content="${siteBaseUrl}/icon-512.png">
<!-- Favicon (简单的 emoji data URI) -->
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📚</text></svg>">
<!-- PWA 支持 🆕 -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#FBBF24">
<!-- iOS Safari PWA 支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="${siteConfig.itemName}学习">
<link rel="apple-touch-icon" href="/icon-192.png">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- 样式 -->
<link rel="stylesheet" href="css/minimal.css">
</head>关键原则:
- ✅ 核心学习模式(闪卡、学习、测试)保持一致 - 参考 core-patterns.md
- ✅ 设计风格(配色、字体、布局)保持一致 - 参考 design-system.md
- ✅ 所有文案、标题、描述由AI根据主题创作
- ✅ 代码质量:必须遵守 code-quality.md 标准(错误处理、XSS防护、DOM安全)🆕
- ✅ PWA 支持:manifest.json + Service Worker + 图标(离线访问、可安装)🆕
- ✅ SEO 优化:完整的 meta 标签 + sitemap.xml + robots.txt + 结构化数据 🆕
- ✅ 语义化 HTML:正确使用 header, main, article, section 等标签 🆕
- ✅ 移动端优先:响应式设计 + viewport meta + 快速加载(< 3秒)🆕
- ❌ 不要硬编码特定领域的内容
---
Step 4: 创建项目结构
# 项目位置
mkdir -p "/Users/joe/Dropbox/code/${topic}-workshop"
cd "/Users/joe/Dropbox/code/${topic}-workshop"
# 创建目录结构
mkdir -p js css
# 写入数据
cat > js/wordData.js << 'EOF'
const WordRoots = [...];
EOF
# 写入配置 🆕
cat > js/siteConfig.js << 'EOF'
const siteConfig = {...};
EOF
# 写入页面(AI生成的HTML)
cat > index.html << 'EOF'
[AI生成的index.html]
EOF
# 写入其他页面...
# 🆕 自动生成 PWA 图标(用 PIL)
python3 << 'PYEOF'
from PIL import Image, ImageDraw, ImageFont
def create_icon(size, filename, text):
# 创建黄色背景
img = Image.new('RGB', (size, size), color='#FBBF24')
draw = ImageDraw.Draw(img)
# 尝试使用系统字体
try:
font_size = int(size * 0.25)
font = ImageFont.truetype('/System/Library/Fonts/PingFang.ttc', font_size)
except:
font = ImageFont.load_default()
# 获取文字边界框
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 居中位置
x = (size - text_width) / 2
y = (size - text_height) / 2
# 绘制文字(深灰色)
draw.text((x, y), text, font=font, fill='#1F2937')
# 保存
img.save(filename, 'PNG')
# 从主题生成图标文字(取前2-3个字)
icon_text = "${siteConfig.itemName}"[:3] # 例如:"概念" → "概念"、"历史知识点" → "历史知"
# 生成两种尺寸
create_icon(192, 'icon-192.png', icon_text)
create_icon(512, 'icon-512.png', icon_text)
print("✓ PWA 图标生成完成")
PYEOF---
Step 5: 数据验证(强制质量检查)
⚠️ 关键:AI 生成的数据必须经过完整验证,确保质量和一致性
# ========================================
# 阶段 1:基础结构验证
# ========================================
echo "🔍 验证数据结构..."
# 1.1 检查数据文件存在且变量名正确
if ! grep -q "const WordRoots" js/wordData.js; then
echo "❌ 错误:数据变量名不正确(应为 const WordRoots)"
exit 1
fi
# 1.2 检查配置文件存在且变量名正确
if ! grep -q "const siteConfig" js/siteConfig.js; then
echo "❌ 错误:配置文件缺失(应为 const siteConfig)"
exit 1
fi
# ========================================
# 阶段 2:数据完整性验证
# ========================================
echo "🔍 验证数据完整性..."
# 2.1 使用 Node.js 进行深度验证
node -e "
const fs = require('fs');
// 读取数据文件
const dataContent = fs.readFileSync('js/wordData.js', 'utf-8');
eval(dataContent); // 加载 WordRoots
let errors = [];
let warnings = [];
// 验证数据存在
if (typeof WordRoots === 'undefined') {
console.error('❌ 严重错误:WordRoots 未定义');
process.exit(1);
}
if (!Array.isArray(WordRoots) || WordRoots.length === 0) {
console.error('❌ 严重错误:WordRoots 为空或不是数组');
process.exit(1);
}
console.log(\`📊 数据量:\${WordRoots.length} 个知识点\`);
// 遍历每个知识点进行验证
WordRoots.forEach((item, index) => {
const itemLabel = \`Item #\${item.id || index}\`;
// 必需字段检查
if (!item.id) errors.push(\`\${itemLabel}: 缺少 id\`);
if (!item.root || item.root.trim() === '') errors.push(\`\${itemLabel}: 缺少 root(知识点名称)\`);
if (!item.origin) warnings.push(\`\${itemLabel}: 缺少 origin(分类)\`);
if (!item.meaning || item.meaning.trim() === '') errors.push(\`\${itemLabel}: 缺少 meaning(简短解释)\`);
if (!item.description || item.description.trim() === '') errors.push(\`\${itemLabel}: 缺少 description(详细说明)\`);
// 描述长度检查(应该详细但不过长)
if (item.description && item.description.length < 50) {
warnings.push(\`\${itemLabel}: description 太短(<50字),建议扩展为200-300字\`);
}
if (item.description && item.description.length > 1000) {
warnings.push(\`\${itemLabel}: description 太长(>1000字),建议精简\`);
}
// 例子检查
if (!item.examples || !Array.isArray(item.examples)) {
errors.push(\`\${itemLabel}: 缺少 examples 数组\`);
} else if (item.examples.length < 3) {
errors.push(\`\${itemLabel}: examples 少于3个(当前 \${item.examples.length})\`);
} else {
// 验证每个例子的结构
item.examples.forEach((ex, exIndex) => {
if (!ex.word) errors.push(\`\${itemLabel}.examples[\${exIndex}]: 缺少 word\`);
if (!ex.meaning) errors.push(\`\${itemLabel}.examples[\${exIndex}]: 缺少 meaning\`);
if (!ex.explanation) warnings.push(\`\${itemLabel}.examples[\${exIndex}]: 缺少 explanation\`);
});
}
// 测试题检查
if (!item.quiz) {
warnings.push(\`\${itemLabel}: 缺少 quiz(测试题)\`);
} else {
if (!item.quiz.question || item.quiz.question.trim() === '') {
errors.push(\`\${itemLabel}.quiz: 缺少 question\`);
}
if (!item.quiz.options || !Array.isArray(item.quiz.options)) {
errors.push(\`\${itemLabel}.quiz: 缺少 options 数组\`);
} else if (item.quiz.options.length !== 4) {
errors.push(\`\${itemLabel}.quiz: options 必须是4个(当前 \${item.quiz.options.length})\`);
}
if (typeof item.quiz.correctAnswer !== 'number') {
errors.push(\`\${itemLabel}.quiz: correctAnswer 必须是数字\`);
} else if (item.quiz.correctAnswer < 0 || item.quiz.correctAnswer > 3) {
errors.push(\`\${itemLabel}.quiz: correctAnswer 越界(必须是0-3,当前 \${item.quiz.correctAnswer})\`);
}
}
});
// 输出验证结果
if (errors.length > 0) {
console.error('\\n❌ 发现 ' + errors.length + ' 个错误:');
errors.slice(0, 10).forEach(e => console.error(' - ' + e));
if (errors.length > 10) console.error(\` ... 还有 \${errors.length - 10} 个错误\`);
process.exit(1);
}
if (warnings.length > 0) {
console.warn('\\n⚠️ 发现 ' + warnings.length + ' 个警告:');
warnings.slice(0, 5).forEach(w => console.warn(' - ' + w));
if (warnings.length > 5) console.warn(\` ... 还有 \${warnings.length - 5} 个警告\`);
}
console.log('\\n✓ 数据验证通过');
" || exit 1
# ========================================
# 阶段 3:配置验证
# ========================================
echo "🔍 验证配置..."
node -e "
const fs = require('fs');
const configContent = fs.readFileSync('js/siteConfig.js', 'utf-8');
eval(configContent);
if (typeof siteConfig === 'undefined') {
console.error('❌ siteConfig 未定义');
process.exit(1);
}
// 验证必需字段
const required = ['topic', 'siteName', 'itemName', 'itemCount', 'hero', 'stats', 'footer', 'cta'];
const missing = required.filter(key => !siteConfig[key]);
if (missing.length > 0) {
console.error('❌ siteConfig 缺少字段:' + missing.join(', '));
process.exit(1);
}
// 验证 hero 结构
if (!siteConfig.hero.title || !Array.isArray(siteConfig.hero.title) || siteConfig.hero.title.length !== 3) {
console.error('❌ siteConfig.hero.title 必须是3行数组');
process.exit(1);
}
console.log('✓ 配置验证通过');
" || exit 1
echo ""
echo "✅ 所有验证通过!"---
Step 6: 部署(强制安全检查)
⚠️ 关键:每个项目必须独立部署,绝不共享 GitHub 仓库
部署流程(必须严格按顺序执行):
# ========================================
# 阶段 1:部署前安全检查(必须执行)
# ========================================
# 1.1 检查并移除 Git 远程仓库(防止关联到其他项目的仓库)
if git remote -v 2>/dev/null | grep -q 'origin'; then
echo "⚠️ 警告:检测到 Git 远程仓库,立即移除以避免冲突"
git remote remove origin
echo "✓ 已移除 Git 远程仓库"
fi
# 1.2 初始化本地 Git(仅本地,不推送到 GitHub)
git init
git add .
git commit -m "Initial commit: ${siteName}"
# 1.3 列出所有现有 workshop 项目(用于后续验证)
echo "📋 现有项目列表:"
ls -d /Users/joe/Dropbox/code/*-workshop 2>/dev/null | while read dir; do
PROJECT_NAME=$(basename "$dir")
if [ -f "$dir/.vercel/project.json" ]; then
PROJECT_ID=$(cat "$dir/.vercel/project.json" | jq -r '.projectId' 2>/dev/null || echo "unknown")
echo " - $PROJECT_NAME (projectId: $PROJECT_ID)"
fi
done
# ========================================
# 阶段 2:执行部署
# ========================================
echo "🚀 开始部署到 Vercel..."
vercel --prod --yes 2>&1 | tee /tmp/vercel-deploy-${projectName}.log
DEPLOY_STATUS=$?
if [ $DEPLOY_STATUS -ne 0 ]; then
echo "❌ 部署失败,请检查日志:/tmp/vercel-deploy-${projectName}.log"
exit 1
fi
# ========================================
# 阶段 3:部署后强制验证(必须执行)
# ========================================
echo ""
echo "🔍 部署后验证..."
# 3.1 验证 projectId 已生成
if [ ! -f ".vercel/project.json" ]; then
echo "❌ 错误:未找到 .vercel/project.json"
exit 1
fi
NEW_PROJECT_ID=$(cat .vercel/project.json | jq -r '.projectId')
NEW_PROJECT_NAME=$(cat .vercel/project.json | jq -r '.projectName')
echo "✓ 新项目:"
echo " 名称: $NEW_PROJECT_NAME"
echo " ID: $NEW_PROJECT_ID"
# 3.2 提取部署 URL
DEPLOY_URL=$(grep -E "Production:|https://.*vercel.app" /tmp/vercel-deploy-${projectName}.log | grep -o "https://[^ ]*vercel.app" | head -1)
echo "✓ 部署 URL: $DEPLOY_URL"
# 3.3 验证新网站可访问
echo "🌐 验证新网站..."
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$DEPLOY_URL" || echo "000")
if [ "$HTTP_STATUS" = "200" ] || [ "$HTTP_STATUS" = "304" ]; then
echo "✓ 新网站可正常访问 (HTTP $HTTP_STATUS)"
else
echo "⚠️ 警告:新网站返回 HTTP $HTTP_STATUS"
fi
# 3.4 检查其他项目是否受影响(关键步骤)
echo ""
echo "🔍 检查其他项目是否受影响..."
AFFECTED_PROJECTS=0
for dir in /Users/joe/Dropbox/code/*-workshop; do
if [ "$dir" = "/Users/joe/Dropbox/code/${projectName}" ]; then
continue # 跳过当前项目
fi
if [ -f "$dir/.vercel/project.json" ]; then
OLD_PROJECT_NAME=$(basename "$dir")
OLD_PROJECT_ID=$(cat "$dir/.vercel/project.json" | jq -r '.projectId' 2>/dev/null)
# 检查是否有相同的 projectId(这表示冲突)
if [ "$OLD_PROJECT_ID" = "$NEW_PROJECT_ID" ]; then
echo "❌ 严重错误:项目 $OLD_PROJECT_NAME 的 projectId 与新项目相同!"
echo " 这意味着新项目覆盖了旧项目,需要立即修复。"
AFFECTED_PROJECTS=$((AFFECTED_PROJECTS + 1))
fi
fi
done
if [ $AFFECTED_PROJECTS -gt 0 ]; then
echo ""
echo "❌ 检测到 $AFFECTED_PROJECTS 个项目受影响,部署失败!"
echo " 请手动检查并修复冲突。"
exit 1
fi
echo "✓ 所有现有项目未受影响"
# ========================================
# 阶段 3.5:移动端和 SEO 验证 🆕
# ========================================
echo ""
echo "📱 验证移动端适配..."
# 检查 viewport meta 标签(移动端必需)
VIEWPORT_CHECK=$(curl -s "$DEPLOY_URL" | grep -c 'viewport')
if [ "$VIEWPORT_CHECK" -gt 0 ]; then
echo "✓ 移动端 viewport 配置正确"
else
echo "⚠️ 警告:缺少 viewport meta 标签,移动端可能显示异常"
fi
# 检查 SEO meta 标签
echo "🔍 验证 SEO 配置..."
META_DESCRIPTION=$(curl -s "$DEPLOY_URL" | grep -c 'meta name="description"')
META_OG=$(curl -s "$DEPLOY_URL" | grep -c 'property="og:')
if [ "$META_DESCRIPTION" -gt 0 ]; then
echo "✓ SEO description 已配置"
else
echo "⚠️ 警告:缺少 SEO description"
fi
if [ "$META_OG" -gt 0 ]; then
echo "✓ Open Graph 标签已配置(社交分享优化)"
else
echo "⚠️ 警告:缺少 Open Graph 标签"
fi
# 模拟移动设备访问测试
echo "📱 模拟移动设备访问..."
MOBILE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" \
"$DEPLOY_URL")
if [ "$MOBILE_STATUS" = "200" ] || [ "$MOBILE_STATUS" = "304" ]; then
echo "✓ 移动端访问正常 (HTTP $MOBILE_STATUS)"
else
echo "⚠️ 警告:移动端访问异常 (HTTP $MOBILE_STATUS)"
fi
# ========================================
# 阶段 4:成功总结
# ========================================
echo ""
echo "✅ 部署成功!"
echo ""
echo "📊 部署信息:"
echo " 项目名称: $NEW_PROJECT_NAME"
echo " 项目 ID: $NEW_PROJECT_ID"
echo " 部署 URL: $DEPLOY_URL"
echo " 日志文件: /tmp/vercel-deploy-${projectName}.log"---
安全原则(必须遵守):
1. ✅ 禁止 GitHub 关联:默认不连接 GitHub,避免仓库共享 2. ✅ 强制前置检查:部署前必须移除所有 Git 远程仓库 3. ✅ 强制后置验证:部署后必须检查 projectId 唯一性 4. ✅ 冲突自动检测:发现冲突立即报错,不允许继续 5. ✅ 完整日志记录:所有部署操作记录到 /tmp/
---
如果仍然发生冲突(极端情况):
如果验证通过但实际仍有问题,执行紧急修复:
# 1. 立即列出所有 Vercel 项目
vercel ls
# 2. 检查每个本地项目的部署状态
cd /Users/joe/Dropbox/code
for dir in *-workshop; do
echo "=== $dir ==="
cd "$dir"
if [ -f ".vercel/project.json" ]; then
cat .vercel/project.json | jq -r '.projectName, .projectId'
fi
cd ..
done
# 3. 重新部署受影响的项目
cd /path/to/affected-project
vercel --prod --yes
# 4. 向用户报告冲突详情和修复结果---
成功输出模板
✅ ${siteName} 已生成并部署!
📁 项目位置:${projectPath}
🌐 网站名称:${siteName}
📚 知识点数量:${itemCount}个
🔗 访问链接:${deployUrl}
🎯 核心特性:
- ✅ AI创作首页:根据主题生成标题、副标题、统计
- ✅ 动态动画:自动从数据加载,完全适配
- ✅ 通用学习模式:闪卡、渐进学习、测试、索引
- ✅ 极简设计:清晰的视觉层级,专注内容
🔧 下一步:
1. 打开网站查看效果
2. 审核AI生成的内容
3. 配置自定义域名(Vercel后台)---
实施检查清单
AI 执行此 skill 时,必须严格按顺序完成:
- [ ] 1. 理解主题 - 分析领域特点、价值、受众、表达方式
- [ ] 2. 生成数据 - 创建 wordData.js(const WordRoots)
- [ ] 3. 生成配置 🆕 - 创建 siteConfig.js(AI创作首页文案)
- [ ] 4. 生成页面 🆕 - 参考设计系统,从零生成HTML(不复制模板)
- [ ] 5. 创建项目 - mkdir + 写入所有文件
- [ ] 6. 验证数据 - 检查数据和配置文件完整性
- [ ] 7. 安全部署 🔒 - 执行 Step 6 的完整部署流程(含前置检查 + 部署 + 后置验证)
- [ ] 8. 返回信息 - 项目路径 + URL + 核心特性 + 安全检查结果
---
关键改进(相比旧版)
❌ 旧版问题
- 依赖模板复制(
cp -r word-root-workshop) - 用 sed 粗暴替换文案
- 首页文案硬编码,不适配主题
- 动画示例写死英文单词
✅ 新版优势
- 零模板依赖,AI从零生成页面
- AI理解主题后创作所有文案
- 首页完全适配主题特点
- 动画自动从数据加载
🎯 核心理念转变
旧版:复制 + 替换
新版:理解 + 创作
旧版:模板驱动
新版:设计系统驱动
旧版:硬编码文案
新版:AI创作内容---
注意事项
⚠️ 数据结构不变:
- 仍然使用
const WordRoots和固定字段结构 - 这是核心学习模式(闪卡、学习、测试)的基础
⚠️ 设计风格保持:
- 极简主义、黄色主题色、Inter字体
- 这些是品牌识别度的保证
⚠️ AI自由发挥:
- 首页文案、统计数据、介绍文本
- 根据主题特点创作,不要千篇一律
---
Vercel 部署最佳实践 🆕
问题背景
Vercel 在部署时可能会自动连接 GitHub 仓库,导致多个项目共享同一个仓库,引发部署冲突:
- 新项目覆盖旧项目的部署
- 旧项目的 URL 失效
- GitHub 仓库关联混乱
解决方案
1. 默认不连接 GitHub
# 仅使用本地 Git,不推送到 GitHub
git init
git add .
git commit -m "Initial commit"
vercel --prod --yes # 只部署,不连接 GitHub2. 部署后验证
# 检查生成的 projectId 是否唯一
cat .vercel/project.json
# 应该看到类似:
# {"projectId":"prj_UNIQUE_ID_HERE",...}3. 发现冲突时的补救
如果部署后发现旧项目受影响:
# 立即进入旧项目目录
cd /Users/joe/Dropbox/code/旧项目名称
# 重新部署旧项目
vercel --prod --yes
# 确认旧项目恢复正常
curl -I https://旧项目URL故障排查清单
部署新项目后,必须检查:
- [ ] 新项目的
.vercel/project.json中的projectId是否唯一 - [ ] 新项目的 Production URL 是否可以访问(HTTP 200)
- [ ] 旧项目(如果存在)的 URL 是否仍然可访问
- [ ] 部署日志中的 "Linked to" 信息是否正确
经验教训(真实生产事故)
事故时间线:2026-02-25
事故描述: 1. 生成 evolutionary-psychology-workshop(进化心理学),成功部署 2. 生成 design-aesthetics-workshop(设计美学),成功部署 3. 用户发现 word.qiaomu.ai(原词根词缀网站)显示的是进化心理学内容 4. 检查发现 word-root-workshop 项目的 Git 仓库被进化心理学内容覆盖
根本原因分析:
# 事故前的状态
evolutionary-psychology-workshop → origin: https://github.com/joeseesun/word-root-workshop.git
word-root-workshop → origin: https://github.com/joeseesun/word-root-workshop.git
↑↑↑ 两个项目共享同一个 GitHub 仓库为什么会发生:
- 旧版 skill 使用
cp -r word-root-workshop复制模板 - 复制时连
.git/目录也一起复制了(包含远程仓库配置) - 部署时 Vercel 检测到 Git 远程仓库,自动关联
- 多个项目关联同一个 GitHub 仓库,后部署的覆盖先部署的
损害范围:
- ⛔ word.qiaomu.ai(生产域名)显示错误内容
- ⛔ word-root-workshop 的 Git 历史被污染
- ⛔ 用户体验受损,需要紧急修复
紧急修复步骤:
# 1. 恢复 word-root-workshop 到原始状态
cd /Users/joe/Dropbox/code/word-root-workshop
git log --oneline # 找到原始提交
git reset --hard 14cc7b0 # 恢复到原始词根词缀内容
# 2. 修复 vercel.json 配置冲突
# 移除不兼容的 routes 配置
# 3. 重新部署
vercel --prod --yes
# 4. 验证恢复
curl -sL https://word.qiaomu.ai/ | grep "词根词缀记忆工坊"
# 5. 清理其他项目的 Git 远程仓库
cd /Users/joe/Dropbox/code/evolutionary-psychology-workshop
git remote remove origin彻底解决方案(已在 Step 6 实施):
1. 强制前置检查:部署前自动移除所有 Git 远程仓库 2. 强制后置验证:部署后检查 projectId 唯一性 3. 冲突自动检测:遍历所有项目,发现相同 projectId 立即报错 4. 完整日志记录:所有部署操作记录到 /tmp/ 5. 零容忍策略:任何检测到的冲突都不允许继续
长期防范措施:
- ✅ 废弃模板复制机制(
cp -r),改用从零生成 - ✅ 在 Step 6 中实施强制安全检查
- ✅ 更新实施检查清单,明确"安全部署"步骤
- ✅ 文档中增加"经验教训"章节,防止后人重蹈覆辙
教训总结:
"Copy + Paste 是万恶之源。模板驱动看似高效,实则埋下了隐患。只有从零生成(设计系统驱动),才能确保每个项目真正独立。"
影响:
- 促使 skill 从"模板驱动"彻底重构为"设计系统驱动"
- 确立了"零模板依赖"的核心原则
- 建立了完善的部署安全检查机制
---
批量更新机制 🔄
使用场景
当 skill 的设计系统有更新(如 CSS bug 修复、样式改进)时,需要将更新同步到所有已部署的 workshop 项目。
典型场景:
- CSS 样式修复:修复了响应式布局问题
- 设计改进:优化了卡片阴影、间距、配色
- 功能增强:添加了新的交互动效
- 安全更新:修复了 XSS 漏洞或其他安全问题
更新脚本
脚本位置:scripts/update-css.sh
使用方法:
# 1. 演练模式(仅列出将更新的项目)
bash scripts/update-css.sh --dry-run
# 2. 执行更新
bash scripts/update-css.sh工作流程
脚本自动执行以下步骤:
1. 扫描项目:自动扫描 /Users/joe/Dropbox/code/*-workshop 2. 智能对比:使用 cmp 命令对比 CSS 文件,跳过已是最新版本的项目 3. 安全备份:更新前自动备份旧 CSS 为 .backup 文件 4. Git 提交:自动 commit CSS 变更(commit message: chore: update CSS from skill template) 5. 重新部署:调用 vercel --prod --yes 重新部署到生产环境 6. 失败回滚:如果部署失败,自动恢复备份的 CSS 7. 统计报告:输出更新统计(成功/跳过/失败项目数量)
使用示例
步骤 1:准备最新的 CSS
在 skill 目录中修复或改进 templates/minimal.css:
cd /Users/joe/.claude/skills/knowledge-site-creator
# 编辑 templates/minimal.css
# 修复 bug 或改进样式步骤 2:预览将更新的项目
bash scripts/update-css.sh --dry-run输出示例:
🔍 演练模式(不会实际更新)
📁 CSS 源文件:/path/to/templates/minimal.css
📊 文件大小:12345 bytes
🔍 扫描 workshop 项目...
================================================
📦 项目:evolutionary-psychology-workshop
📝 将更新:/path/to/evolutionary-psychology-workshop/css/minimal.css
📝 将重新部署到 Vercel
================================================
📦 项目:word-root-workshop
✓ 跳过:CSS 已是最新版本
================================================
📊 更新总结
✅ 成功更新:1 个项目
⚠️ 跳过:1 个项目
💡 这是演练模式,没有实际执行任何操作
要实际执行,请运行:bash scripts/update-css.sh步骤 3:执行批量更新
bash scripts/update-css.sh输出示例:
================================================
📦 项目:evolutionary-psychology-workshop
💾 已备份旧 CSS:/path/to/css/minimal.css.backup
✓ 已更新 CSS
🚀 重新部署到 Vercel...
✅ 部署成功
================================================
📊 更新总结
✅ 成功更新:1 个项目
⚠️ 跳过:1 个项目安全特性
1. 智能跳过:自动跳过已是最新版本的项目,避免不必要的部署 2. 自动备份:更新前备份旧 CSS 为 .backup 文件 3. 失败回滚:部署失败时自动恢复备份 4. Git 记录:所有更新都有 Git commit,可追溯历史 5. 演练模式:--dry-run 模式让你先看看会更新什么
注意事项
⚠️ 更新前检查:
- 确保
templates/minimal.css已经过测试 - 使用
--dry-run先预览将更新的项目 - 检查是否有项目正在被用户访问(避免高峰期更新)
⚠️ 更新后验证:
- 脚本完成后,随机抽查 2-3 个项目的网站
- 确认新样式生效且没有破坏布局
- 检查移动端显示是否正常
⚠️ 失败处理:
- 如果某个项目部署失败,脚本会自动回滚该项目的 CSS
- 失败的项目不影响其他项目的更新
- 可以手动进入失败的项目目录,使用
vercel --prod --yes重试
扩展性
未来可扩展的更新类型:
当前脚本仅支持 CSS 更新,但同样的机制可以扩展到:
- JavaScript 文件更新(
js/storage.js等) - HTML 模板更新(如修复 meta 标签缺失)
- 配置文件更新(
vercel.json等) - 批量迁移(如数据结构变更)
扩展方法:参考 update-css.sh 创建类似脚本,如 update-storage.sh、update-meta-tags.sh 等。
# macOS
.DS_Store
# Editor
.vscode/
.idea/
# Temporary files
*.tmp
*.log
*~
# Python
__pycache__/
*.py[cod]
*$py.class
MIT License
Copyright (c) 2026 Joe (向阳乔木)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"skills": [
{
"name": "knowledge-site-creator",
"path": "SKILL.md",
"description": "一句话生成任何领域的知识学习网站。AI自动理解主题、创作内容、生成页面、部署上线。",
"tags": ["content-creation", "web-development", "education", "ai-automation"],
"author": {
"name": "Joe (向阳乔木)",
"url": "https://github.com/joeseesun"
},
"examples": [
"生成一个进化心理学学习网站",
"创建量子力学基础概念网站",
"做个中医经络穴位学习工坊"
],
"requirements": {
"tools": ["vercel", "python3", "PIL"],
"platforms": ["macos", "linux"]
}
}
]
}
Knowledge Site Creator
一句话生成任何领域的知识学习网站 - AI 理解主题,自动创作内容,生成页面,一键部署
 
✨ 特性
- 🤖 AI 全自动创作:理解主题 → 生成数据 → 创作文案 → 部署上线
- 📚 通用学习模式:闪卡、渐进学习、测试、索引、进度追踪
- 🎨 极简设计系统:黄色主题、Inter 字体、响应式布局
- 📱 PWA 支持:自动生成图标、离线访问、可安装
- 🔍 SEO 优化:完整 meta 标签、sitemap、结构化数据
- ✅ 代码质量:XSS 防护、错误处理、DOM 安全
- 🚀 零模板依赖:AI 参考设计系统从零生成页面
🎯 适用领域
适用于任何需要系统学习的知识领域:
- 📖 学科知识:进化心理学、量子力学、中医经络
- 💻 技术术语:大模型术语、编程概念、设计原则
- 🏛️ 历史文化:五代十国、唐诗宋词、世界历史
- 🔬 科学概念:化学元素、物理定律、生物分类
- 🎨 设计美学:CRAP 原则、配色理论、版式设计
🚀 快速开始
前置要求
- Claude Code CLI
- Node.js 18+
- Vercel CLI(可选,用于部署)
安装
方法一:使用 npx skills(推荐)
# 自动安装到 Claude Code
npx skills add joeseesun/knowledge-site-creator方法二:手动安装
# 克隆仓库
git clone https://github.com/joeseesun/knowledge-site-creator.git
# 复制到 Claude Code skills 目录
cp -r knowledge-site-creator ~/.claude/skills/方法三:直接复制(如果已有本地副本)
cp -r /path/to/knowledge-site-creator ~/.claude/skills/使用
在 Claude Code 中直接说:
生成一个进化心理学学习网站或者:
创建量子力学基础概念网站AI 会自动: 1. 分析主题特点和价值 2. 生成 20-30 个核心知识点数据 3. 创作首页文案、统计、介绍 4. 参考设计系统生成页面 5. 部署到 Vercel 6. 返回访问链接
📂 项目结构
knowledge-site-creator/
├── SKILL.md # 主 Skill 文件(AI 执行流程)
├── README.md # 本文件
├── references/ # 设计规范和模式参考
│ ├── core-patterns.md # 核心学习模式(闪卡、测试等)
│ ├── design-system.md # 设计系统(配色、字体、间距)
│ ├── code-quality.md # 代码质量标准
│ ├── pwa-setup.md # PWA 配置指南
│ └── seo-best-practices.md # SEO 优化最佳实践
└── scripts/ # 辅助脚本
└── update-css.sh # CSS 更新脚本🎨 设计系统
配色方案
- 主题色:
#FBBF24(黄色) - 成功色:
#10B981(绿色) - 错误色:
#EF4444(红色) - 文字色:
#1F2937(深灰) - 背景色:
#FFFFFF(白色)
核心学习模式
1. 闪卡(Flashcard):卡片翻转、键盘控制(←→空格)、进度显示 2. 学习(Learn):渐进式展示、上下翻页、自动保存进度 3. 测试(Quiz):4 选 1 测试题、Toast 反馈、答案解析 4. 索引(Index):搜索筛选、卡片网格、已掌握标记 5. 进度(Progress):学习统计、已掌握列表、进度条
📊 示例网站
- 五代十国历史工坊 - 从后梁到后周,系统学习五代十国历史脉络
- 设计美学学习工坊 - 掌握 CRAP 原则,提升设计品味
- 词根词缀记忆工坊 - 通过词根拆解,高效记忆英语单词
- 大模型术语学习工坊 - AI 时代必备,30 个核心概念速览
🛠️ 技术栈
- 前端:原生 HTML/CSS/JavaScript(零依赖)
- 样式:CSS 变量 + 8px 网格系统
- 存储:LocalStorage(进度管理)
- PWA:manifest.json + Service Worker
- 部署:Vercel(一键部署)
- AI:Claude Code(内容创作)
📝 核心原则
1. 设计系统优先:复用设计语言,不复用具体页面代码 2. AI 创作内容:所有文案、统计、介绍都由 AI 根据主题创作 3. 零模板依赖:AI 参考设计系统从零生成页面 4. 代码质量:XSS 防护、错误处理、DOM 安全 5. 用户体验:Toast 反馈、键盘快捷键、响应式设计
🔧 高级配置
自定义主题色
编辑生成的 css/minimal.css:
:root {
--color-primary: #FBBF24; /* 改成你想要的颜色 */
}修改知识点数量
在生成时明确告诉 AI:
生成一个进化心理学网站,包含 50 个核心概念自定义数据结构
编辑生成的 js/wordData.js,按照固定格式添加知识点。
🤝 贡献
欢迎贡献代码、报告 Bug、提出建议!
1. Fork 本仓库 2. 创建特性分支 (git checkout -b feature/AmazingFeature) 3. 提交改动 (git commit -m 'Add some AmazingFeature') 4. 推送到分支 (git push origin feature/AmazingFeature) 5. 提交 Pull Request
📄 License
本项目采用 MIT 协议开源 - 详见 LICENSE 文件
👨💻 作者
向阳乔木(Joe)
- X (Twitter): @vista8
- 微信公众号:「向阳乔木推荐看」
- GitHub: @joeseesun
<p align="center"> <img src="https://github.com/joeseesun/terminal-boost/raw/main/assets/wechat-qr.jpg?raw=true" alt="向阳乔木推荐看公众号二维码" width="200"> </p>
---
如果这个项目对你有帮助,请给个 ⭐️ Star 支持一下!
代码质量标准
目标:生成健壮、可维护、高性能的网站代码
一级标准(必须达到)🔴
这些是必须遵守的规则,不遵守会导致 bug 或安全问题。
1. 错误处理
❌ 错误示例
// 没有错误处理,localStorage 可能返回 null
const progress = JSON.parse(localStorage.getItem('progress'));
progress.masteredRoots.forEach(...); // 💥 崩溃✅ 正确示例
function getProgress() {
try {
const data = localStorage.getItem('progress');
if (!data) {
return getDefaultProgress();
}
const parsed = JSON.parse(data);
// 验证数据结构
if (!parsed.masteredRoots || !Array.isArray(parsed.masteredRoots)) {
return getDefaultProgress();
}
return parsed;
} catch (error) {
console.error('Failed to load progress:', error);
return getDefaultProgress();
}
}
function getDefaultProgress() {
return {
masteredRoots: [],
currentRootIndex: 0,
lastStudyDate: null
};
}规则:
- LocalStorage 读取必须有 try-catch
- JSON.parse 必须有错误处理
- 必须提供默认值 fallback
---
2. XSS 防护
❌ 危险示例
// 直接插入用户数据,有 XSS 风险
container.innerHTML = `
<div class="root-name">${root.root}</div>
<div class="root-meaning">${root.meaning}</div>
`;
// 如果 root.root = "<script>alert('XSS')</script>",脚本会被执行!✅ 安全示例(方案1:textContent)
// 使用 textContent(推荐)
function createRootCard(root) {
const card = document.createElement('div');
card.className = 'root-card';
const nameDiv = document.createElement('div');
nameDiv.className = 'root-name';
nameDiv.textContent = root.root; // 安全:textContent 会自动转义
const meaningDiv = document.createElement('div');
meaningDiv.className = 'root-meaning';
meaningDiv.textContent = root.meaning;
card.appendChild(nameDiv);
card.appendChild(meaningDiv);
return card;
}✅ 安全示例(方案2:escapeHtml)
// 如果必须使用 innerHTML,先转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
container.innerHTML = `
<div class="root-name">${escapeHtml(root.root)}</div>
<div class="root-meaning">${escapeHtml(root.meaning)}</div>
`;规则:
- 优先使用 textContent/createElement
- 如果必须用 innerHTML,先用 escapeHtml 转义
- 永远不要直接插入未经处理的用户数据
---
3. DOM 安全查询
❌ 不安全示例
// 没有检查元素是否存在
document.getElementById('rootName').textContent = root.root; // 💥 如果元素不存在会崩溃✅ 安全示例
// 方案1:检查元素存在
function updateRootName(text) {
const element = document.getElementById('rootName');
if (element) {
element.textContent = text;
} else {
console.warn('Element #rootName not found');
}
}
// 方案2:使用可选链(Optional Chaining)
document.getElementById('rootName')?.textContent = root.root;规则:
- 所有 DOM 操作前检查元素是否存在
- 或使用可选链
?.语法
---
4. 边界检查
❌ 重复的边界检查
// 每个函数都检查边界
function loadRoot(index) {
if (index < 0 || index >= WordRoots.length) return;
// ...
}
function nextRoot() {
if (currentIndex < WordRoots.length - 1) {
loadRoot(currentIndex + 1);
}
}✅ 消除边界检查(Linus 的"好品味")
// 使用环形索引,消除边界情况
function normalizeIndex(index, length) {
return ((index % length) + length) % length;
}
function loadRoot(index) {
const safeIndex = normalizeIndex(index, WordRoots.length);
const root = WordRoots[safeIndex];
// ... 不再需要边界检查
}
function nextRoot() {
loadRoot(currentIndex + 1); // 自动循环到开头
}
function prevRoot() {
loadRoot(currentIndex - 1); // 自动循环到末尾
}Linus 视角:
"消除特殊情况,让代码无条件执行。边界检查是糟糕设计的补丁。"
---
5. 避免全局变量污染
❌ 全局变量污染
// 污染全局作用域
let currentIndex = 0;
let currentRoot = null;
function loadRoot(index) { ... }
function nextRoot() { ... }✅ 模块化封装
// 使用 IIFE 或模块模式
const AppState = (() => {
let currentIndex = 0;
let currentRoot = null;
return {
getCurrentIndex() { return currentIndex; },
setCurrentIndex(index) { currentIndex = index; },
getCurrentRoot() { return currentRoot; },
setCurrentRoot(root) { currentRoot = root; }
};
})();
// 或者使用现代 ES6 模块(如果支持)
class AppState {
#currentIndex = 0;
#currentRoot = null;
getCurrentIndex() { return this.#currentIndex; }
setCurrentIndex(index) { this.#currentIndex = index; }
}规则:
- 避免全局变量,使用模块封装
- 或至少使用命名空间(如
window.APP = {})
---
二级标准(建议遵守)🟡
这些规则提升代码质量,但不是强制的。
6. 性能优化
❌ 重复查询 DOM
function updateProgress(index) {
document.getElementById('progressBar').style.width = '50%';
document.getElementById('progressBar').setAttribute('aria-valuenow', 50);
document.getElementById('progressBar').textContent = '50%';
}✅ 缓存 DOM 引用
// 初始化时缓存
const DOM = {
progressBar: document.getElementById('progressBar'),
rootName: document.getElementById('rootName'),
// ...
};
function updateProgress(percentage) {
if (DOM.progressBar) {
DOM.progressBar.style.width = `${percentage}%`;
DOM.progressBar.setAttribute('aria-valuenow', percentage);
DOM.progressBar.textContent = `${percentage}%`;
}
}✅ 使用事件委托
// ❌ 为每个按钮添加监听器
buttons.forEach(btn => {
btn.addEventListener('click', handleClick);
});
// ✅ 事件委托
document.getElementById('container').addEventListener('click', (e) => {
if (e.target.classList.contains('quiz-option')) {
handleClick(e.target);
}
});---
7. 代码可读性
✅ 函数单一职责
// ❌ 函数做太多事情
function loadRoot(index) {
const root = WordRoots[index];
updateUI(root);
updateProgress(index);
saveToLocalStorage(index);
logAnalytics(index);
}
// ✅ 拆分职责
function loadRoot(index) {
const root = getRoot(index);
renderRoot(root);
updateProgress(index);
}
function renderRoot(root) {
updateRootUI(root);
renderExamples(root.examples);
renderQuiz(root.quiz);
}✅ 语义化命名
// ❌ 缩写和不清晰的命名
const idx = 0;
const r = getRt(idx);
const m = r.m;
// ✅ 清晰的命名
const currentIndex = 0;
const root = getRoot(currentIndex);
const meaning = root.meaning;✅ 配置与逻辑分离
// ❌ 魔法数字硬编码
setInterval(updateDemo, 4000);
const examples = WordRoots.slice(0, 5);
// ✅ 提取为配置
const CONFIG = {
ANIMATION_INTERVAL: 4000,
DEMO_EXAMPLES_COUNT: 5
};
setInterval(updateDemo, CONFIG.ANIMATION_INTERVAL);
const examples = WordRoots.slice(0, CONFIG.DEMO_EXAMPLES_COUNT);---
8. 现代化 JavaScript
✅ 使用 const/let 代替 var
// ❌ 使用 var(作用域混乱)
var currentIndex = 0;
// ✅ 使用 const/let
let currentIndex = 0;
const maxIndex = WordRoots.length - 1;✅ 使用解构赋值
// ❌ 逐个取值
const root = WordRoots[index];
const name = root.root;
const meaning = root.meaning;
const origin = root.origin;
// ✅ 解构赋值
const { root: name, meaning, origin } = WordRoots[index];✅ 使用箭头函数
// 简洁的箭头函数
const getMasteredCount = () => {
const progress = getProgress();
return progress.masteredRoots.length;
};
// 数组操作更简洁
const masteredRoots = WordRoots.filter(root =>
progress.masteredRoots.includes(root.id)
);---
检查清单 ✅
生成代码后,必须检查:
一级标准(强制)
- [ ] 所有 LocalStorage 读取有 try-catch
- [ ] 所有 DOM 操作前检查元素存在
- [ ] 没有直接使用 innerHTML 插入未转义数据
- [ ] 边界情况已消除或有检查
- [ ] 没有全局变量污染
二级标准(建议)
- [ ] DOM 引用已缓存
- [ ] 使用事件委托代替多个监听器
- [ ] 函数职责单一(<20行)
- [ ] 变量命名语义化
- [ ] 魔法数字提取为配置
- [ ] 使用现代 JavaScript 语法
---
快速参考
| 问题 | 解决方案 |
|---|---|
| LocalStorage 崩溃 | try-catch + fallback 默认值 |
| XSS 攻击 | 使用 textContent 或 escapeHtml |
| DOM 查询失败 | 检查元素存在 或 ?. |
| 重复边界检查 | 重新设计数据结构 |
| 全局变量冲突 | 模块封装 或 IIFE |
| 重复 DOM 查询 | 缓存 DOM 引用 |
| 多个事件监听器 | 事件委托 |
| 函数太长 | 拆分职责(单一职责原则) |
---
Linus 语录
"Bad programmers worry about the code. Good programmers worry about data structures and their relationships."
>
"消除特殊情况,让代码无条件执行。边界检查是糟糕设计的补丁。"
>
"如果你需要超过3层缩进,你就已经完蛋了,应该修复你的程序。"
核心学习模式 - 功能参考
通用的学习功能模式,在所有知识网站中保持一致
0. 用户反馈系统原则 ⚠️ 强制规范
❌ 禁止使用系统对话框:
alert()- 阻塞式、难看、不可定制confirm()- 仅在真正需要确认时保留(如重置进度)prompt()- 从不使用
✅ 正确的反馈方式:
1. 成功/错误提示 → 使用 showFeedback(message, isSuccess)
// ✓ 正确
showFeedback('✓ 回答正确!', true);
showFeedback('✗ 操作失败', false);
// ✗ 错误
alert('回答正确!');2. 错误跳转前的提示 → 使用 console.warn() 静默记录
// ✓ 正确
if (!data) {
console.warn('数据未找到,跳转回首页');
window.location.href = '/';
return;
}
// ✗ 错误
if (!data) {
alert('数据未找到');
window.location.href = '/';
}3. 确认操作(谨慎使用) → 仅保留 confirm() 用于破坏性操作
// ✓ 可接受(破坏性操作)
if (confirm('确定要删除所有数据吗?此操作无法撤销。')) {
localStorage.clear();
showFeedback('✓ 数据已清除', true);
}`showFeedback()` 函数定义(每个需要反馈的页面都要包含):
function showFeedback(message, isSuccess) {
const toast = document.createElement('div');
toast.className = `feedback-toast ${isSuccess ? 'success' : 'error'}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}---
1. 数据结构(通用)
所有知识网站使用相同的数据结构:
// js/wordData.js
const WordRoots = [
{
id: 1,
root: "知识点名称",
origin: "分类/来源",
meaning: "一句话解释",
description: "详细说明(200-300字)",
examples: [
{
word: "例子名称",
meaning: "例子含义",
breakdown: { root: "知识点名称" },
explanation: "详细解释"
}
],
quiz: {
question: "测试问题?",
options: ["选项A", "选项B", "选项C", "选项D"],
correctAnswer: 2 // 正确答案索引(0-3)
}
}
];2. 网站配置(AI创作)
// js/siteConfig.js
const siteConfig = {
topic: "主题名称",
siteName: "网站名称",
itemName: "知识点名称",
itemCount: 30,
hero: {
title: ["第一行", "第二行", "第三行"],
subtitle: "副标题",
animation: {
enabled: true,
demoCount: 5
}
},
stats: [
{ value: "30", label: "核心概念" },
{ value: "100+", label: "应用场景" },
{ value: "15分钟", label: "每日学习" }
],
footer: {
tagline: "一句话口号",
description: "2-3句介绍"
},
cta: {
primary: "开始第一个概念 →",
secondary: "闪卡复习"
}
};3. 本地存储(进度管理)⚠️ 代码质量标准
关键原则:
- ✅ 错误处理:try-catch 包裹所有 LocalStorage 操作
- ✅ 数据验证:检查数据结构完整性
- ✅ 默认值:提供安全的 fallback
// js/storage.js
const StorageManager = {
// 获取默认进度
_getDefaultProgress() {
return {
masteredRoots: [],
currentRootIndex: 0,
lastStudyDate: null
};
},
// 验证进度数据结构
_validateProgress(data) {
return (
data &&
typeof data === 'object' &&
Array.isArray(data.masteredRoots) &&
typeof data.currentRootIndex === 'number'
);
},
// 获取进度(带错误处理)
getProgress() {
try {
const data = localStorage.getItem('progress');
if (!data) {
return this._getDefaultProgress();
}
const parsed = JSON.parse(data);
if (!this._validateProgress(parsed)) {
console.warn('Invalid progress data, using default');
return this._getDefaultProgress();
}
return parsed;
} catch (error) {
console.error('Failed to load progress:', error);
return this._getDefaultProgress();
}
},
// 保存进度(带错误处理)
_saveProgress(progress) {
try {
localStorage.setItem('progress', JSON.stringify(progress));
return true;
} catch (error) {
console.error('Failed to save progress:', error);
return false;
}
},
// 标记为已掌握
markRootAsMastered(rootId) {
const progress = this.getProgress();
if (!progress.masteredRoots.includes(rootId)) {
progress.masteredRoots.push(rootId);
progress.lastStudyDate = new Date().toISOString();
this._saveProgress(progress);
}
return progress;
},
// 更新学习进度
updateProgress(rootIndex) {
const progress = this.getProgress();
progress.currentRootIndex = rootIndex;
progress.lastStudyDate = new Date().toISOString();
this._saveProgress(progress);
return progress;
}
};4. 闪卡模式(Flashcard)
4.1 核心功能
- 卡片翻转:正面显示知识点名称,背面显示详细信息
- 键盘控制:←→切换卡片,空格翻转
- 进度显示:当前进度 + 已掌握数量
- 标记功能:标记为已掌握
4.2 实现模式
// 卡片翻转
function flipCard() {
const card = document.getElementById('flashcard');
card.classList.toggle('flipped');
}
// 加载卡片
function loadCard(index) {
const root = WordRoots[index];
// 正面
document.getElementById('frontRoot').textContent = root.root;
document.getElementById('frontMeaning').textContent = root.meaning;
document.getElementById('frontOrigin').textContent = root.origin;
// 背面
document.getElementById('backRoot').textContent = root.root;
document.getElementById('backDescription').textContent = root.description;
// 更新进度
updateProgress(index);
}
// 键盘控制
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') prevCard();
if (e.key === 'ArrowRight') nextCard();
if (e.key === ' ') flipCard();
});4.3 HTML结构
<div class="flashcard" id="flashcard" onclick="flipCard()">
<div class="flashcard-inner">
<!-- 正面 -->
<div class="flashcard-front">
<div class="flashcard-root" id="frontRoot">-</div>
<div class="flashcard-meaning" id="frontMeaning">-</div>
<div class="flashcard-origin" id="frontOrigin">-</div>
</div>
<!-- 背面 -->
<div class="flashcard-back">
<div class="flashcard-root" id="backRoot">-</div>
<div class="flashcard-description" id="backDescription">-</div>
<div class="flashcard-examples" id="backExamples">
<!-- 动态生成 -->
</div>
</div>
</div>
</div>5. 渐进学习模式(Learn)
5.1 核心功能
- 渐进式展示:一次一个知识点
- 上一个/下一个:顺序学习
- 标记已掌握:学习过程中可标记
- 自动保存进度:记录当前位置
5.2 实现模式
// 加载知识点
function loadRoot(index) {
if (index < 0 || index >= WordRoots.length) return;
const root = WordRoots[index];
currentIndex = index;
// 更新标题
document.getElementById('rootName').textContent = root.root;
document.getElementById('rootMeaning').textContent = root.meaning;
// 更新详细说明
document.getElementById('description').textContent = root.description;
// 渲染例子
renderExamples(root.examples);
// 渲染测试题
renderQuiz(root.quiz);
// 更新进度
updateProgress(index);
}
// 导航
function nextRoot() {
if (currentIndex < WordRoots.length - 1) {
loadRoot(currentIndex + 1);
}
}
function prevRoot() {
if (currentIndex > 0) {
loadRoot(currentIndex - 1);
}
}6. 测试题模式(Quiz)
6.1 实现模式
// 渲染测试题(安全方法)
function renderQuiz(quiz) {
const questionElement = document.getElementById('quizQuestion');
if (questionElement) {
questionElement.textContent = quiz.question;
}
const optionsContainer = document.getElementById('quizOptions');
if (!optionsContainer) {
console.warn('Element #quizOptions not found');
return;
}
// 清空容器
optionsContainer.innerHTML = '';
// 创建选项按钮(安全方法)
quiz.options.forEach((option, index) => {
const button = document.createElement('button');
button.className = 'quiz-option';
button.textContent = option; // textContent 安全
button.onclick = () => checkAnswer(index);
optionsContainer.appendChild(button);
});
}
// 显示反馈提示(Toast)⚠️ 不要用 alert!
function showFeedback(message, isSuccess) {
const toast = document.createElement('div');
toast.className = `feedback-toast ${isSuccess ? 'success' : 'error'}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
// 检查答案
function checkAnswer(selectedIndex) {
const quiz = currentRoot.quiz;
const isCorrect = selectedIndex === quiz.correctAnswer;
// 更新选项样式
const options = document.querySelectorAll('.quiz-option');
options.forEach((btn, index) => {
btn.disabled = true;
if (index === quiz.correctAnswer) {
btn.classList.add('correct');
} else if (index === selectedIndex && !isCorrect) {
btn.classList.add('wrong');
}
});
// 显示反馈(使用优雅的 toast,不要用 alert!)
if (isCorrect) {
setTimeout(() => {
showFeedback('✓ 回答正确!', true);
}, 300);
} else {
setTimeout(() => {
showFeedback('✗ 回答错误,正确答案是:' + quiz.options[quiz.correctAnswer], false);
}, 300);
}
}7. 索引模式(Index)
7.1 核心功能
- 搜索:按名称搜索知识点
- 筛选:按分类筛选
- 卡片展示:网格布局显示所有知识点
- 已掌握标记:显示学习状态
7.2 实现模式
// 搜索功能
function searchRoots(query) {
const filtered = WordRoots.filter(root =>
root.root.toLowerCase().includes(query.toLowerCase()) ||
root.meaning.toLowerCase().includes(query.toLowerCase())
);
renderRoots(filtered);
}
// 筛选功能
function filterByOrigin(origin) {
if (origin === 'all') {
renderRoots(WordRoots);
} else {
const filtered = WordRoots.filter(root => root.origin === origin);
renderRoots(filtered);
}
}
// 渲染知识点卡片(XSS 安全)
function renderRoots(roots) {
const container = document.getElementById('rootsGrid');
if (!container) {
console.warn('Element #rootsGrid not found');
return;
}
const progress = StorageManager.getProgress();
const masteredIds = new Set(progress.masteredRoots);
// 清空容器
container.innerHTML = '';
// 创建卡片(使用 createElement 避免 XSS)
roots.forEach(root => {
const card = createRootCard(root, masteredIds.has(root.id));
container.appendChild(card);
});
}
// 创建单个卡片(安全方法)
function createRootCard(root, isMastered) {
const card = document.createElement('div');
card.className = `root-card ${isMastered ? 'mastered' : ''}`;
card.onclick = () => viewDetail(root.id);
const nameDiv = document.createElement('div');
nameDiv.className = 'root-name';
nameDiv.textContent = root.root; // textContent 自动转义,安全
const meaningDiv = document.createElement('div');
meaningDiv.className = 'root-meaning';
meaningDiv.textContent = root.meaning;
const originDiv = document.createElement('div');
originDiv.className = 'root-origin';
originDiv.textContent = root.origin;
card.appendChild(nameDiv);
card.appendChild(meaningDiv);
card.appendChild(originDiv);
if (isMastered) {
const badge = document.createElement('span');
badge.className = 'mastered-badge';
badge.textContent = '✓ 已掌握'; // 明确的文字标识
card.appendChild(badge);
}
return card;
}8. 进度追踪模式(Progress)
8.1 实现模式
// 加载进度
function loadProgress() {
const progress = StorageManager.getProgress();
const masteredCount = progress.masteredRoots.length;
const totalCount = WordRoots.length;
const percentage = Math.round((masteredCount / totalCount) * 100);
// 更新统计
document.getElementById('masteredCount').textContent = masteredCount;
document.getElementById('totalCount').textContent = totalCount;
document.getElementById('percentage').textContent = percentage;
// 更新进度条
document.getElementById('progressBar').style.width = `${percentage}%`;
// 显示已掌握列表
renderMasteredList(progress.masteredRoots);
}9. 首页动画(自动适配)
9.1 动态加载示例
// 从数据自动提取前5个作为动画示例
const examples = WordRoots.slice(0, 5).map(root => {
const firstExample = root.examples[0];
return {
word: root.root,
prefix: firstExample?.breakdown?.prefix || '',
prefixMeaning: '',
root: root.root,
rootMeaning: root.meaning,
meaning: firstExample?.meaning || root.description.substring(0, 30) + '...'
};
});
// 动画循环
let currentIndex = 0;
function updateDemo() {
const example = examples[currentIndex];
// 更新内容
document.getElementById('demoWord').textContent = example.word;
document.getElementById('demoPrefix').textContent = example.prefix;
document.getElementById('demoRoot').textContent = example.root;
document.getElementById('demoMeaning').textContent = example.meaning;
// 切换下一个
currentIndex = (currentIndex + 1) % examples.length;
}
// 每4秒切换
setInterval(updateDemo, 4000);10. 关键原则
1. 数据驱动:所有页面从 WordRoots 数据加载,不硬编码 2. 进度持久化:使用 LocalStorage 保存学习进度 3. 键盘友好:支持键盘快捷键(←→空格) 4. 响应式设计:适配移动端和桌面端 5. 极简交互:清晰的反馈,最少的操作步骤
知识学习网站 - 设计系统参考
从 word-root-workshop 提取的设计规范,供AI生成新页面时参考
1. 设计风格
极简主义(Minimalism):
- Less is More,去除一切多余元素
- 清晰的视觉层级,专注内容本身
- 大留白,呼吸感
2. 配色方案
:root {
/* 主色 */
--color-accent: #FBBF24; /* 黄色主题色 */
--color-accent-dark: #F59E0B; /* 深黄色(hover) */
/* 状态色 */
--color-success: #10B981; /* 绿色(成功/已掌握) */
--color-error: #EF4444; /* 红色(错误/警告) */
/* 文字 */
--color-text: #0F172A; /* 深灰主文字 */
--color-text-secondary: #64748B; /* 次要文字 */
--color-text-tertiary: #94A3B8; /* 三级文字 */
/* 背景 */
--color-bg: #FFFFFF; /* 白色背景 */
--color-bg-secondary: #F8FAFC; /* 浅灰背景 */
--color-bg-hover: #F8FAFC; /* hover背景 */
/* 边框 */
--color-border: #E2E8F0; /* 边框颜色 */
}3. 字体系统
/* 字体族 */
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
/* 代码/术语字体 */
font-family: 'Courier New', monospace;
/* 标题 */
h1 { font-size: 3rem; font-weight: 700; } /* 48px */
h2 { font-size: 2rem; font-weight: 600; } /* 32px */
h3 { font-size: 1.5rem; font-weight: 600; } /* 24px */
/* 正文 */
body { font-size: 1rem; line-height: 1.6; } /* 16px */4. 间距系统(8px网格)
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
--space-3xl: 64px;
--space-4xl: 96px;5. 阴影系统(极浅)
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.02);
--shadow-md: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
--shadow-lg: 0 4px 16px 0 rgba(0, 0, 0, 0.06);6. 圆角
--radius: 8px; /* 小圆角 */
--radius-lg: 12px; /* 大圆角 */7. 过渡动画
--transition: 200ms cubic-bezier(0.4, 0, 0.2, 1);8. 组件样式
8.1 按钮
.btn {
padding: 12px 24px;
border-radius: var(--radius);
font-weight: 600;
transition: var(--transition);
cursor: pointer;
border: none;
}
.btn-primary {
background: var(--color-accent);
color: var(--color-text);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(251, 191, 36, 0.3);
}
.btn-large {
padding: 16px 32px;
font-size: 1.125rem;
}8.2 卡片
.card {
background: white;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 24px;
transition: var(--transition);
}
.card:hover {
border-color: var(--color-accent);
box-shadow: var(--shadow-md);
}8.3 标签和徽章 🆕
分类标签(origin标签):
.root-origin {
display: inline-block;
background: var(--color-accent); /* 黄色主题色 */
color: var(--color-text);
padding: 4px 12px;
border-radius: var(--radius-sm);
font-size: 14px;
margin-top: var(--spacing-xs);
}已掌握徽章(mastered-badge):
.mastered-badge {
display: inline-block;
background: var(--color-success); /* 绿色,区分于分类标签 */
color: #FFFFFF; /* 白色文字 */
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: 12px;
margin-top: var(--spacing-xs);
margin-left: var(--spacing-xs); /* 与前面的标签保持间距 */
}设计原则:
- ✅ 使用不同颜色区分功能(黄色=分类,绿色=状态)
- ✅ 添加左边距避免拥挤(
margin-left: 8px) - ✅ 文字清晰明确("✓ 已掌握" 而不是只有 "✓")
8.4 导航栏
.nav {
border-bottom: 1px solid var(--color-border);
padding: 16px 0;
background: white;
}
.nav-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.nav-brand {
font-weight: 600;
color: var(--color-text);
font-size: 1.125rem;
text-decoration: none; /* 移除下划线 */
transition: color 0.2s;
}
.nav-brand:hover {
color: var(--color-primary-dark); /* 悬停变色 */
}
.nav-link {
color: var(--color-text-secondary);
font-weight: 500;
transition: var(--transition);
}
.nav-link:hover,
.nav-link.active {
color: var(--color-text);
}⚠️ 重要:.nav-brand 必须是 <a href="/"> 链接,不是 <div>,点击可回到首页。
9. 布局模式
9.1 Hero区(首页)
.hero {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-4xl) 0; /* 96px 垂直留白 */
text-align: center;
}
.hero-title {
font-size: 4rem; /* 64px */
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.02em;
margin-bottom: var(--space-lg);
}
.hero-subtitle {
font-size: 1.25rem;
color: var(--color-text-secondary);
margin-bottom: var(--space-2xl);
}9.2 统计卡片(3列网格)
.stat-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-xl);
margin: var(--space-4xl) 0;
}
.stat-card {
text-align: center;
padding: var(--space-2xl);
}
.stat-value {
font-size: 3rem;
font-weight: 700;
color: var(--color-text);
}
.stat-label {
font-size: 1rem;
color: var(--color-text-secondary);
margin-top: var(--space-sm);
}9.3 内容容器
.container {
max-width: 800px; /* 主要内容最大宽度 */
margin: 0 auto;
padding: 0 var(--space-lg);
}
.container-wide {
max-width: 1200px; /* 宽版容器 */
}10. 响应式断点
/* Mobile */
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
.stat-grid {
grid-template-columns: 1fr;
gap: var(--space-lg);
}
}11. 使用原则
1. 颜色使用:
- 主题色(黄色):主要CTA、重点元素、hover状态
- 灰色系:文字、边框、背景
- 避免过多颜色,保持极简
2. 间距使用:
- 遵循8px网格系统
- 组件内间距:16px-24px
- 组件间间距:32px-48px
- 页面留白:96px
3. 阴影使用:
- 极浅阴影,不要太重
- 仅用于卡片、按钮hover等需要层次感的地方
4. 圆角使用:
- 小元素(按钮、标签):8px
- 大元素(卡片、容器):12px
5. 动画使用:
- 使用统一的过渡时间(200ms)
- hover效果:轻微上移 + 阴影
- 避免过度动画
PWA(Progressive Web App)配置指南
让知识学习网站支持离线访问和安装到主屏幕
PWA 核心特性
1. Service Worker:缓存静态资源,支持离线访问 2. Web App Manifest:定义 app 元数据(图标、名称、颜色) 3. HTTPS:PWA 必需(Vercel 自动提供) 4. 响应式设计:已有(viewport meta)
---
1. Web App Manifest
manifest.json
放置在项目根目录:
{
"name": "${siteConfig.siteName}",
"short_name": "${siteConfig.itemName}学习",
"description": "${siteConfig.footer.description}",
"start_url": "/",
"display": "standalone",
"background_color": "#FFFFFF",
"theme_color": "#FBBF24",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"categories": ["education", "productivity"],
"lang": "zh-CN"
}字段说明
| 字段 | 说明 | 值 |
|---|---|---|
name | 完整名称 | ${siteConfig.siteName} |
short_name | 主屏幕显示的短名称 | ${siteConfig.itemName}学习 |
start_url | 启动 URL | / |
display | 显示模式 | standalone(全屏,无浏览器 UI) |
background_color | 启动画面背景色 | #FFFFFF |
theme_color | 主题色(地址栏) | #FBBF24(黄色) |
---
2. Service Worker
sw.js
放置在项目根目录:
// Service Worker 版本(每次修改内容时更新)
const CACHE_VERSION = 'v1';
const CACHE_NAME = `${siteConfig.topic}-${CACHE_VERSION}`;
// 需要缓存的静态资源
const STATIC_ASSETS = [
'/',
'/index.html',
'/learn.html',
'/flashcard.html',
'/roots.html',
'/progress.html',
'/root-detail.html',
'/css/minimal.css',
'/js/wordData.js',
'/js/siteConfig.js',
'/js/storage.js',
'/manifest.json'
];
// 安装事件:缓存静态资源
self.addEventListener('install', (event) => {
console.log('[SW] Installing Service Worker...');
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('[SW] Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
.then(() => {
// 强制激活新的 Service Worker
return self.skipWaiting();
})
.catch((error) => {
console.error('[SW] Cache failed:', error);
})
);
});
// 激活事件:清理旧缓存
self.addEventListener('activate', (event) => {
console.log('[SW] Activating Service Worker...');
event.waitUntil(
caches.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('[SW] Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(() => {
// 立即接管所有页面
return self.clients.claim();
})
);
});
// 拦截请求:缓存优先策略
self.addEventListener('fetch', (event) => {
// 只处理 GET 请求
if (event.request.method !== 'GET') {
return;
}
event.respondWith(
caches.match(event.request)
.then((cachedResponse) => {
// 如果缓存存在,直接返回
if (cachedResponse) {
console.log('[SW] Serving from cache:', event.request.url);
return cachedResponse;
}
// 缓存不存在,发起网络请求
console.log('[SW] Fetching from network:', event.request.url);
return fetch(event.request)
.then((networkResponse) => {
// 如果是静态资源,缓存一份
if (event.request.url.includes(self.location.origin)) {
const responseClone = networkResponse.clone();
caches.open(CACHE_NAME)
.then((cache) => {
cache.put(event.request, responseClone);
});
}
return networkResponse;
})
.catch((error) => {
console.error('[SW] Fetch failed:', error);
// 返回离线页面(可选)
return new Response('Offline - Please check your connection', {
status: 503,
statusText: 'Service Unavailable',
headers: new Headers({
'Content-Type': 'text/plain'
})
});
});
})
);
});
// 消息事件:支持手动更新缓存
self.addEventListener('message', (event) => {
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
});缓存策略说明
Cache First(缓存优先): 1. 先查缓存 2. 有缓存 → 直接返回(快!) 3. 无缓存 → 网络请求 → 缓存一份 → 返回
适用场景:静态资源(HTML、CSS、JS、图片)
---
3. 注册 Service Worker
在所有 HTML 页面的 <head> 中添加
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- iOS Safari 支持 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="${siteConfig.short_name}">
<link rel="apple-touch-icon" href="/icon-192.png">
<!-- 主题色 -->
<meta name="theme-color" content="#FBBF24">在所有 HTML 页面的 </body> 前添加
<script>
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('SW registered:', registration.scope);
// 监听更新
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 有新版本,提示用户刷新
console.log('New version available! Refresh to update.');
// 可选:显示更新提示
showUpdateNotification();
}
});
});
})
.catch((error) => {
console.error('SW registration failed:', error);
});
});
}
// 显示更新提示(可选)
function showUpdateNotification() {
const banner = document.createElement('div');
banner.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: #FBBF24;
color: #000;
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 9999;
font-size: 14px;
cursor: pointer;
`;
banner.textContent = '发现新版本,点击更新';
banner.onclick = () => {
window.location.reload();
};
document.body.appendChild(banner);
// 10秒后自动隐藏
setTimeout(() => {
banner.remove();
}, 10000);
}
</script>---
4. 生成图标
图标规格
PWA 需要两个尺寸的图标:
icon-192.png(192x192)icon-512.png(512x512)
简单方案:使用 Emoji 作为图标
# 创建 icon 生成脚本
cat > generate-icons.sh << 'EOF'
#!/bin/bash
# 使用 ImageMagick 从 emoji 生成图标
convert -background "#FBBF24" -fill white -font "Arial-Unicode-MS" \
-size 192x192 -gravity center label:"📚" icon-192.png
convert -background "#FBBF24" -fill white -font "Arial-Unicode-MS" \
-size 512x512 -gravity center label:"📚" icon-512.png
echo "✓ Icons generated"
EOF
chmod +x generate-icons.sh
./generate-icons.sh或使用在线工具
- PWA Asset Generator: https://www.pwabuilder.com/imageGenerator
- 上传一张正方形图片(至少 512x512)
- 自动生成所有需要的尺寸
---
5. 测试 PWA
Chrome DevTools
1. 打开 Chrome DevTools(F12) 2. 切换到 Application 标签 3. 检查:
- Manifest:查看 manifest.json 是否正确加载
- Service Workers:查看 SW 是否注册成功
- Cache Storage:查看缓存的资源
Lighthouse
1. DevTools → Lighthouse 标签 2. 选择 Progressive Web App 类别 3. 点击 Generate report 4. 目标分数:90+ / 100
手机测试
Android Chrome: 1. 访问网站 2. 点击右上角菜单 → "安装应用" 3. 安装到主屏幕
iOS Safari: 1. 访问网站 2. 点击分享按钮 → "添加到主屏幕" 3. 图标会出现在主屏幕
---
6. 常见问题
Q1: Service Worker 不更新?
解决方案:
// 强制更新 Service Worker
navigator.serviceWorker.getRegistration().then((registration) => {
registration.update();
});
// 或者直接跳过等待
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});Q2: iOS 不显示"添加到主屏幕"?
检查清单:
- ✅ 必须使用 HTTPS(Vercel 自动提供)
- ✅ 必须有
apple-touch-icon - ✅ 必须有
<meta name="apple-mobile-web-app-capable"> - ✅ 网站必须被用户访问至少 30 秒
Q3: 缓存太多,如何清理?
用户端清理: 1. Chrome:设置 → 隐私和安全 → 清除浏览数据 → 缓存 2. Safari:设置 → Safari → 清除历史记录和网站数据
开发者清理:
// 清除所有缓存
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => caches.delete(cacheName))
);
});---
7. PWA 最佳实践
✅ 必须做
1. HTTPS:PWA 强制要求(Vercel 自动提供) 2. 响应式设计:适配所有设备(已有) 3. 快速加载:首屏加载 < 3 秒(Service Worker 缓存) 4. 离线可用:至少首页可离线访问
⚠️ 建议做
1. 更新提示:检测到新版本时提示用户刷新 2. 骨架屏:加载时显示内容骨架(而不是空白) 3. 懒加载:图片和非首屏内容懒加载
❌ 不要做
1. 不要缓存用户数据:LocalStorage 中的进度数据不应通过 SW 缓存 2. 不要缓存 API 请求:动态数据应该实时获取 3. 不要忘记更新版本号:修改内容后必须更新 CACHE_VERSION
---
8. Vercel 部署配置
vercel.json 添加 PWA 支持
{
"headers": [
{
"source": "/sw.js",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
},
{
"key": "Service-Worker-Allowed",
"value": "/"
}
]
},
{
"source": "/manifest.json",
"headers": [
{
"key": "Content-Type",
"value": "application/manifest+json"
}
]
}
]
}重要:Service Worker 文件(sw.js)必须设置 Cache-Control: no-cache,确保浏览器每次都检查是否有新版本。
---
快速检查清单 ✅
部署 PWA 后,检查:
- [ ] manifest.json 可访问(浏览器打开
/manifest.json) - [ ] sw.js 可访问(浏览器打开
/sw.js) - [ ] 图标已生成(icon-192.png, icon-512.png)
- [ ] Chrome DevTools → Application → Manifest 显示正确
- [ ] Service Worker 注册成功(Console 有 "SW registered" 日志)
- [ ] Lighthouse PWA 分数 > 90
- [ ] 手机上可以"安装到主屏幕"
- [ ] 断网后首页仍可访问(离线测试)
---
Linus 视角:PWA 的价值
"好的 PWA 应该是不可见的。用户不应该感觉到它是网页还是原生 app。如果你的 PWA 需要向用户解释什么是 Service Worker,那你就失败了。它应该就是'能用',而且'很快'。"
核心原则:
- ✅ 透明:用户无感知,自动生效
- ✅ 快速:缓存让加载接近瞬时
- ✅ 可靠:离线也能基本可用
- ❌ 不要过度工程化:不需要复杂的缓存策略
SEO 最佳实践 - 知识网站优化指南
让知识网站被搜索引擎收录,提升自然流量
核心原则
1. 语义化 HTML - 正确使用标签(h1, h2, article, section) 2. 元数据完整 - Title, Description, Keywords, OG标签 3. 结构化数据 - Schema.org 标记(JSON-LD) 4. 内容优化 - 清晰的标题层级,关键词密度 5. 性能优化 - 快速加载(已有PWA缓存) 6. 移动友好 - 响应式设计(已有)
---
1. 基础元数据(所有页面)
1.1 通用 Meta 标签
<head>
<!-- 基础 SEO -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${pageTitle} - ${siteConfig.siteName}</title>
<meta name="description" content="${pageDescription}">
<meta name="keywords" content="${siteConfig.topic}, 学习, 教育, ${siteConfig.itemName}">
<meta name="author" content="${siteConfig.author || '向阳乔木'}">
<!-- 语言和地区 -->
<meta name="language" content="zh-CN">
<link rel="canonical" href="${currentPageUrl}">
<!-- 搜索引擎指令 -->
<meta name="robots" content="index, follow">
<meta name="googlebot" content="index, follow">
<meta name="bingbot" content="index, follow">
</head>1.2 Open Graph(社交媒体分享)
<!-- Open Graph (Facebook, LinkedIn) -->
<meta property="og:type" content="website">
<meta property="og:url" content="${currentPageUrl}">
<meta property="og:title" content="${pageTitle} - ${siteConfig.siteName}">
<meta property="og:description" content="${pageDescription}">
<meta property="og:image" content="${siteConfig.ogImage || '/icon-512.png'}">
<meta property="og:locale" content="zh_CN">
<meta property="og:site_name" content="${siteConfig.siteName}">1.3 Twitter Card
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="${siteConfig.twitterHandle || '@vista8'}">
<meta name="twitter:creator" content="${siteConfig.twitterHandle || '@vista8'}">
<meta name="twitter:title" content="${pageTitle} - ${siteConfig.siteName}">
<meta name="twitter:description" content="${pageDescription}">
<meta name="twitter:image" content="${siteConfig.twitterImage || '/icon-512.png'}">---
2. 结构化数据(Schema.org)
2.1 网站信息(首页 - index.html)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "${siteConfig.siteName}",
"description": "${siteConfig.footer.description}",
"url": "${siteBaseUrl}",
"author": {
"@type": "Person",
"name": "${siteConfig.author || '向阳乔木'}",
"url": "https://x.com/vista8"
},
"inLanguage": "zh-CN",
"potentialAction": {
"@type": "SearchAction",
"target": "${siteBaseUrl}/roots.html?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}
</script>2.2 教育课程(首页 - index.html)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Course",
"name": "${siteConfig.siteName}",
"description": "${siteConfig.footer.description}",
"provider": {
"@type": "Organization",
"name": "${siteConfig.author || '向阳乔木'}",
"url": "https://x.com/vista8"
},
"educationalLevel": "Beginner",
"inLanguage": "zh-CN",
"numberOfLessons": ${siteConfig.itemCount},
"coursePrerequisites": "无",
"hasCourseInstance": {
"@type": "CourseInstance",
"courseMode": "online",
"courseWorkload": "PT15M"
}
}
</script>2.3 知识点详情(root-detail.html)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "${root.root}",
"description": "${root.meaning}",
"articleBody": "${root.description}",
"author": {
"@type": "Person",
"name": "${siteConfig.author || '向阳乔木'}"
},
"datePublished": "${siteConfig.publishDate || new Date().toISOString()}",
"dateModified": "${new Date().toISOString()}",
"inLanguage": "zh-CN",
"educationalUse": "学习",
"typicalAgeRange": "16-"
}
</script>2.4 面包屑导航(适用于详情页)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "首页",
"item": "${siteBaseUrl}"
},
{
"@type": "ListItem",
"position": 2,
"name": "${siteConfig.itemName}索引",
"item": "${siteBaseUrl}/roots.html"
},
{
"@type": "ListItem",
"position": 3,
"name": "${root.root}",
"item": "${siteBaseUrl}/root-detail.html?id=${root.id}"
}
]
}
</script>---
3. 语义化 HTML 结构
3.1 首页结构
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- Meta 标签 -->
</head>
<body>
<!-- Header -->
<header role="banner">
<nav role="navigation" aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/learn.html">学习</a></li>
<li><a href="/roots.html">索引</a></li>
</ul>
</nav>
</header>
<!-- Hero 区域 -->
<main role="main">
<section class="hero" aria-label="网站介绍">
<h1>${siteConfig.hero.title.join(' ')}</h1>
<p class="subtitle">${siteConfig.hero.subtitle}</p>
</section>
<!-- 统计卡片 -->
<section class="stats" aria-label="网站统计">
<div class="stat-card">
<h2 class="stat-value">${stats[0].value}</h2>
<p class="stat-label">${stats[0].label}</p>
</div>
</section>
</main>
<!-- Footer -->
<footer role="contentinfo">
<p>${siteConfig.footer.tagline}</p>
<p>${siteConfig.footer.description}</p>
</footer>
</body>
</html>3.2 详情页结构
<main role="main">
<article itemscope itemtype="https://schema.org/Article">
<header>
<h1 itemprop="headline">${root.root}</h1>
<p itemprop="description">${root.meaning}</p>
</header>
<section itemprop="articleBody">
<h2>详细说明</h2>
<p>${root.description}</p>
</section>
<section>
<h2>应用例子</h2>
<ul>
${root.examples.map(ex => `<li>${ex.word}: ${ex.meaning}</li>`).join('')}
</ul>
</section>
<section>
<h2>小测验</h2>
<div role="group" aria-label="选择题">
<p>${root.quiz.question}</p>
<!-- 选项 -->
</div>
</section>
</article>
</main>---
4. sitemap.xml(动态生成)
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<!-- 首页 -->
<url>
<loc>${siteBaseUrl}/</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<!-- 功能页面 -->
<url>
<loc>${siteBaseUrl}/learn.html</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>${siteBaseUrl}/flashcard.html</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>${siteBaseUrl}/roots.html</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>${siteBaseUrl}/progress.html</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<!-- 所有知识点详情页 -->
${WordRoots.map(root => `
<url>
<loc>${siteBaseUrl}/root-detail.html?id=${root.id}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
`).join('')}
</urlset>生成 sitemap.xml 的脚本
// js/generate-sitemap.js
const fs = require('fs');
// 从 wordData.js 导入数据(如果在 Node.js 环境)
const WordRoots = [...]; // 或者 require('./wordData.js')
const siteConfig = require('./siteConfig.js');
const siteBaseUrl = 'https://your-site.vercel.app'; // 实际部署后的 URL
const staticPages = [
{ url: '/', priority: '1.0', changefreq: 'daily' },
{ url: '/learn.html', priority: '0.8', changefreq: 'weekly' },
{ url: '/flashcard.html', priority: '0.8', changefreq: 'weekly' },
{ url: '/roots.html', priority: '0.9', changefreq: 'daily' },
{ url: '/progress.html', priority: '0.7', changefreq: 'weekly' },
];
const dynamicPages = WordRoots.map(root => ({
url: `/root-detail.html?id=${root.id}`,
priority: '0.6',
changefreq: 'monthly'
}));
const allPages = [...staticPages, ...dynamicPages];
const lastmod = new Date().toISOString().split('T')[0];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${allPages.map(page => ` <url>
<loc>${siteBaseUrl}${page.url}</loc>
<lastmod>${lastmod}</lastmod>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>`).join('\n')}
</urlset>`;
fs.writeFileSync('sitemap.xml', sitemap);
console.log('✓ sitemap.xml generated');---
5. robots.txt
# robots.txt
User-agent: *
Allow: /
# Sitemap 位置
Sitemap: ${siteBaseUrl}/sitemap.xml
# 禁止抓取(可选)
Disallow: /js/
Disallow: /css/
# 爬虫速率限制(可选)
Crawl-delay: 1---
6. 页面特定优化
6.1 首页(index.html)
Title: ${siteConfig.siteName} - ${siteConfig.itemCount}个核心${siteConfig.itemName}系统学习
Description: ${siteConfig.footer.description}(140-160字符)
H1: ${siteConfig.hero.title.join(' ')}(只用一次)
关键词密度:
- 主关键词(${siteConfig.topic})出现 3-5 次
- 次关键词(学习、${siteConfig.itemName})出现 2-3 次
6.2 学习页(learn.html)
Title: 渐进学习 - ${siteConfig.siteName}
Description: 系统学习${siteConfig.itemCount}个${siteConfig.itemName},从基础到进阶,一次一个概念,轻松掌握${siteConfig.topic}核心知识。
6.3 索引页(roots.html)
Title: ${siteConfig.itemName}索引 - ${siteConfig.siteName}
Description: 浏览所有${siteConfig.itemCount}个${siteConfig.itemName},支持搜索和分类筛选,快速找到你需要的${siteConfig.topic}知识点。
6.4 详情页(root-detail.html)
Title: ${root.root} - ${root.meaning} | ${siteConfig.siteName}
Description: 深入学习${root.root}:${root.description.substring(0, 120)}...
H1: ${root.root}
内容优化:
- 使用
<h2>分段(详细说明、应用例子、小测验) - 关键词密度 2-3%
- 内容长度 500+ 字
---
7. 性能优化(SEO 相关)
7.1 图片优化
<!-- 使用现代图片格式 + 懒加载 -->
<img
src="/images/example.webp"
alt="描述性文字(包含关键词)"
loading="lazy"
width="800"
height="600"
>7.2 关键资源预加载
<head>
<!-- 预加载关键资源 -->
<link rel="preload" href="/css/minimal.css" as="style">
<link rel="preload" href="/js/wordData.js" as="script">
<!-- DNS 预解析(如果有外部资源)-->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
</head>7.3 Service Worker 缓存策略
已在 pwa-setup.md 中实现:
- 静态资源缓存优先
- 首次加载 < 3 秒
- Lighthouse Performance > 90
---
8. 提交到搜索引擎
8.1 Google Search Console
1. 访问 https://search.google.com/search-console 2. 添加资源(域名或 URL 前缀) 3. 验证所有权(DNS 或 HTML 文件) 4. 提交 sitemap.xml 5. 请求索引(首次)
8.2 Bing Webmaster Tools
1. 访问 https://www.bing.com/webmasters 2. 添加站点 3. 验证所有权 4. 提交 sitemap.xml
8.3 百度站长平台
1. 访问 https://ziyuan.baidu.com 2. 添加网站 3. 验证网站 4. 提交 sitemap 5. 主动推送(可选)
---
9. SEO 检查清单 ✅
部署后检查:
基础 SEO
- [ ] 每个页面有唯一的
<title>(50-60字符) - [ ] 每个页面有
<meta name="description">(140-160字符) - [ ] H1 标签只用一次,包含主关键词
- [ ] 语义化 HTML(header, main, footer, article, section)
- [ ] 所有图片有 alt 属性
- [ ] 内部链接正确(相对路径)
技术 SEO
- [ ] robots.txt 存在且正确
- [ ] sitemap.xml 存在且包含所有页面
- [ ] 所有页面响应式(移动友好)
- [ ] HTTPS 启用(Vercel 自动)
- [ ] 页面加载速度 < 3 秒
- [ ] 没有 404 错误
结构化数据
- [ ] 首页有 WebSite schema
- [ ] 首页有 Course schema
- [ ] 详情页有 Article schema
- [ ] 详情页有 BreadcrumbList schema
社交媒体
- [ ] Open Graph 标签完整
- [ ] Twitter Card 标签完整
- [ ] 分享图片(og:image)存在且美观
提交
- [ ] 提交到 Google Search Console
- [ ] 提交到 Bing Webmaster Tools
- [ ] 提交到百度站长平台(可选)
---
10. 常见问题
Q1: 页面不被索引?
检查清单: 1. robots.txt 是否允许抓取 2. sitemap.xml 是否提交 3. 是否有 <meta name="robots" content="noindex">(移除) 4. 内容是否原创且有价值(避免重复内容)
Q2: 排名不高?
优化方向: 1. 内容质量:增加深度、原创性、实用性 2. 关键词优化:标题、描述、H1/H2 包含目标关键词 3. 外部链接:在其他网站(如公众号文章)添加链接 4. 用户体验:降低跳出率,增加停留时间
Q3: 如何提升 Lighthouse SEO 分数?
关键因素:
- ✅ 文档有
<title> - ✅ 文档有
<meta name="description"> - ✅ 链接有可访问的名称(text 或 aria-label)
- ✅ 图片有 alt 属性
- ✅ 文档有
<meta name="viewport"> - ✅ 移动端字体大小 >= 12px
---
Linus 视角:SEO 的工程化
"SEO 不是魔法,而是工程。好的 SEO 来自于好的网站架构、清晰的内容结构、快速的加载速度。那些试图'欺骗'搜索引擎的做法都是垃圾。"
核心原则:
- ✅ 内容为王:高质量内容自然排名高
- ✅ 技术基础:快速、安全、可访问
- ✅ 语义化:让搜索引擎理解你的内容
- ❌ 不要耍小聪明:关键词堆砌、隐藏文本、购买链接
SEO = 用户体验:
- 用户喜欢的,搜索引擎也喜欢
- 快速加载、清晰导航、移动友好
- 好内容会自然获得外部链接
#!/bin/bash
#
# 批量更新所有 workshop 的 CSS 样式
# 用途:当 skill 的 CSS 有 bug 修复或样式改进时,一键更新所有已部署的网站
#
# 使用方法:
# bash scripts/update-css.sh [--dry-run]
#
# 选项:
# --dry-run 仅列出将要更新的项目,不实际执行
#
set -e # 遇到错误立即退出
DRY_RUN=false
if [ "$1" = "--dry-run" ]; then
DRY_RUN=true
echo "🔍 演练模式(不会实际更新)"
echo ""
fi
# CSS 源文件位置(需要先准备好最新的 CSS)
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CSS_SOURCE="$SKILL_DIR/templates/minimal.css"
if [ ! -f "$CSS_SOURCE" ]; then
echo "❌ 错误:未找到 CSS 源文件:$CSS_SOURCE"
echo " 请先创建或复制最新的 CSS 文件到 templates/ 目录"
exit 1
fi
echo "📁 CSS 源文件:$CSS_SOURCE"
echo "📊 文件大小:$(wc -c < "$CSS_SOURCE") bytes"
echo ""
# 查找所有 workshop 项目
WORKSHOPS_DIR="/Users/joe/Dropbox/code"
UPDATED_COUNT=0
SKIPPED_COUNT=0
FAILED_COUNT=0
echo "🔍 扫描 workshop 项目..."
echo ""
for dir in "$WORKSHOPS_DIR"/*-workshop; do
if [ ! -d "$dir" ]; then
continue
fi
PROJECT_NAME=$(basename "$dir")
CSS_TARGET="$dir/css/minimal.css"
echo "================================================"
echo "📦 项目:$PROJECT_NAME"
# 检查是否有 CSS 文件
if [ ! -f "$CSS_TARGET" ]; then
echo "⚠️ 跳过:未找到 css/minimal.css"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
echo ""
continue
fi
# 检查 CSS 是否相同(避免不必要的更新)
if cmp -s "$CSS_SOURCE" "$CSS_TARGET"; then
echo "✓ 跳过:CSS 已是最新版本"
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
echo ""
continue
fi
if [ "$DRY_RUN" = true ]; then
echo "📝 将更新:$CSS_TARGET"
echo "📝 将重新部署到 Vercel"
UPDATED_COUNT=$((UPDATED_COUNT + 1))
echo ""
continue
fi
# 备份旧 CSS
cp "$CSS_TARGET" "$CSS_TARGET.backup"
echo "💾 已备份旧 CSS:$CSS_TARGET.backup"
# 复制新 CSS
cp "$CSS_SOURCE" "$CSS_TARGET"
echo "✓ 已更新 CSS"
# Git 提交
cd "$dir"
git add css/minimal.css
git commit -m "chore: update CSS from skill template" || echo "⚠️ 无 Git 变更"
# 重新部署
echo "🚀 重新部署到 Vercel..."
if vercel --prod --yes > /dev/null 2>&1; then
echo "✅ 部署成功"
UPDATED_COUNT=$((UPDATED_COUNT + 1))
else
echo "❌ 部署失败"
FAILED_COUNT=$((FAILED_COUNT + 1))
# 恢复备份
cp "$CSS_TARGET.backup" "$CSS_TARGET"
echo "↩️ 已恢复备份"
fi
echo ""
done
echo "================================================"
echo "📊 更新总结"
echo ""
echo " ✅ 成功更新:$UPDATED_COUNT 个项目"
echo " ⚠️ 跳过:$SKIPPED_COUNT 个项目"
if [ $FAILED_COUNT -gt 0 ]; then
echo " ❌ 失败:$FAILED_COUNT 个项目"
fi
echo ""
if [ "$DRY_RUN" = true ]; then
echo "💡 这是演练模式,没有实际执行任何操作"
echo " 要实际执行,请运行:bash scripts/update-css.sh"
fi
Related skills
How it compares
Pick this over generic static-site generators when you need flashcards, quizzes, PWA, and Vercel deploy from markdown without manual page authoring.
FAQ
What does knowledge-site-creator produce?
knowledge-site-creator produces a deployed Vercel knowledge site with siteConfig.js, 20–30 concept pages, flashcards, quizzes, a searchable index, and PWA configuration generated from a topic or markdown collection.
How many learning modes does knowledge-site-creator include?
knowledge-site-creator implements four learning modes—flashcards with keyboard shortcuts, progressive learn pages, knowledge quizzes, and a searchable concept index—guided by five bundled reference documents.
How popular is knowledge-site-creator on skills.sh?
knowledge-site-creator shows 91 installs and rank 67 on skills.sh, indicating steady adoption among developers publishing AI-generated learning sites from notes or wikis.