feat: add llms.txt AI navigation file and update README with OpenClaw support

docs: remove outdated multimodal API chapter and cleanup scripts

refactor: update modern component library docs with shadcn/ui extensions
This commit is contained in:
sanbuphy
2026-03-02 20:11:08 +08:00
parent 9a4e1544c3
commit 25909511df
8 changed files with 1418 additions and 221 deletions
+1
View File
@@ -125,6 +125,7 @@
## 🔥 News
- **[2026-03-02]** 🦞 **OpenClaw & AI Agent 友好支持**:新增 `llms.txt` AI 导航文件,让 OpenClaw、Claude、Cursor、Trae 等 AI Agent 能够快速理解本仓库结构,精准定位教程内容。希望每个🦞都学得愉快!
- **[2026-03-01]** [高级开发部分](https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/)全面升级:新增 Claude Code 七大深度指南(MCP、Skills、Agent Teams 等)及八大跨平台开发实战(PWA、Electron、NFT、VS Code 插件、Qt 工业应用等)。
- **[2026-02-25]** 更新[附录知识库](https://datawhalechina.github.io/easy-vibe/zh-cn/appendix/),涵盖 9 大知识领域、80+ 交互式专题。
- **[2026-01-27]** 新增 Android 和 iOS 平台应用开发教程。
@@ -1,3 +0,0 @@
# AI 能力入门手册
> 本章节正在编写中,敬请期待...
@@ -442,3 +442,24 @@ Vibe Coding 的工作流总结:
| [Headless UI](https://headlessui.com) | ~27k | Tailwind Labs 官方出品的无样式组件库,同时支持 React 和 Vue | 搭配 Tailwind CSS 使用 |
| [HeroUI](https://heroui.com) | ~24k | 基于 Tailwind CSS + React Aria,默认样式精美,动画流畅 | 追求视觉品质的项目 |
| [Radix UI](https://www.radix-ui.com) | ~17k | 无样式底层组件原语库,专注无障碍和组件行为,是 shadcn/ui 的底层基础 | 构建自定义设计系统 |
#### shadcn/ui 扩展生态
除了上述通用组件库,shadcn/ui 生态中还涌现了大量基于其理念的扩展库,为特定场景提供差异化选择。这些扩展库同样采用"复制代码到项目"的模式,让开发者拥有完全的源码控制权。
| 组件库 | 简介 | 适用场景 |
| :--- | :--- | :--- |
| [Aceternity UI](https://ui.aceternity.com) | 200+ 生产级组件,主打发光卡片、文字渐变、3D 地球等特色视觉组件 | 高质感落地页、SaaS 产品 |
| [Tailark UI](https://tailark.com) | 营销网站组件块集合,产品展示、客户证言、CTA 按钮等营销高频模块 | 营销落地页、产品官网 |
| [UI Tripled](https://ui.tripled.work) | 基于 Framer Motion 的动态交互组件,弹窗、导航、卡片动画 | 创意工具、个人作品集 |
| [Neobrutalism UI](https://neobrutalism.dev) | 新粗野主义风格,粗线条、高对比度、鲜明色彩 | 个性化品牌官网、创意项目 |
| [REUI](https://reui.io) | 967+ 真实业务场景的组件组合模式 | 企业级后台、复杂表单 |
| [Cult UI](https://cult-ui.com) | 更细的交互/视觉打磨,数据表格、筛选面板等复合组件 | 高质感商业项目 |
| [Kibo UI](https://kibo-ui.com) | 高级业务组件,颜色选择器、富文本编辑器、文件上传等 | 管理后台、工具类产品 |
| [Kokonut UI](https://kokonutui.com) | 100+ 组件 + 7+ 完整模板,清新简约风格 | SaaS 官网、博客、电商 |
| [Commerce UI](https://ui.stackzero.co) | 电商场景专用,商品卡片、购物车、结算表单 | 电商平台 |
| [shadcnblocks](https://shadcnblocks.com) | 1373 个 UI 块 + 13 套完整模板,资源最全面 | 所有场景 |
| [Shoogle](https://shoogle.dev) | shadcn/ui 生态聚合检索平台 | 快速查找资源 |
| [Discover All Shadcn](https://allshadcn.com) | 聚合型资源导航 | 快速查找资源 |
> **为什么选择 shadcn/ui 扩展?** 这些扩展继承了 shadcn/ui"代码所有权"的理念,同时为特定场景做了深度定制。Vibe Coding 时代,它们让你能快速找到符合设计需求的组件,跳出主流 UI 库的同质化,做出更具差异化的产品。
-32
View File
@@ -1,32 +0,0 @@
import fs from 'fs'
import path from 'path'
function walkSync(dir, filelist = []) {
fs.readdirSync(dir).forEach((file) => {
const dirFile = path.join(dir, file)
try {
filelist = fs.statSync(dirFile).isDirectory()
? walkSync(dirFile, filelist)
: filelist.concat(dirFile)
} catch (err) {
if (err.code === 'OOM' || err.code === 'EMFILE') throw err
}
})
return filelist
}
const vueFiles = walkSync('docs/.vitepress/theme/components').filter((f) =>
f.endsWith('.vue')
)
for (const file of vueFiles) {
const content = fs.readFileSync(file, 'utf8')
if (content.includes('setInterval')) {
const lines = content.split('\n')
lines.forEach((line, i) => {
if (line.includes('setInterval')) {
console.log(`${file}:${i + 1}: ${line.trim()}`)
}
})
}
}
-54
View File
@@ -1,54 +0,0 @@
const fs = require('fs')
const path = require('path')
function walk(dir) {
let results = []
const list = fs.readdirSync(dir)
list.forEach(function (file) {
file = dir + '/' + file
const stat = fs.statSync(file)
if (stat && stat.isDirectory()) {
results = results.concat(walk(file))
} else {
if (file.endsWith('.vue')) results.push(file)
}
})
return results
}
const vueFiles = walk('docs/.vitepress/theme/components')
vueFiles.forEach((file) => {
const lines = fs.readFileSync(file, 'utf8').split('\n')
let bracketDepth = 0
let insideScript = false
lines.forEach((line, index) => {
if (line.includes('<script setup')) {
insideScript = true
bracketDepth = 0
return
}
if (line.includes('</script>')) {
insideScript = false
}
if (insideScript) {
// Check for setInterval BEFORE updating bracket depth for the current line
// because `setInterval(() => {` will increase depth but the call ITSELF is at depth 0
if (line.includes('setInterval') && bracketDepth === 0) {
console.log(
`Top-level setInterval: ${file}:${index + 1} - ${line.trim()}`
)
}
// Simple Bracket Depth Counting
// Handle one-liners like `if (x) { ... }` natively by adding/subtracting on the same line
// Wait, we only care about trailing brackets basically. Ignore string contents for simplicity.
// This heuristic is usually fine for formatting standard vue codes
const openCount = (line.match(/\{/g) || []).length
const closeCount = (line.match(/\}/g) || []).length
bracketDepth += openCount - closeCount
}
})
})
-61
View File
@@ -1,61 +0,0 @@
const fs = require('fs')
const path = require('path')
const compiler = require('vue/compiler-sfc')
const babel = require('@babel/parser')
const traverse = require('@babel/traverse').default
function walk(dir) {
let results = []
const list = fs.readdirSync(dir)
list.forEach(function (file) {
file = dir + '/' + file
const stat = fs.statSync(file)
if (stat && stat.isDirectory()) {
results = results.concat(walk(file))
} else {
if (file.endsWith('.vue')) results.push(file)
}
})
return results
}
const vueFiles = walk('docs/.vitepress/theme/components')
vueFiles.forEach((file) => {
const content = fs.readFileSync(file, 'utf8')
if (!content.includes('setInterval')) return
const { descriptor } = compiler.parse(content)
if (!descriptor.scriptSetup) return
const ast = babel.parse(descriptor.scriptSetup.content, {
sourceType: 'module',
plugins: ['typescript']
})
traverse(ast, {
CallExpression(path) {
if (path.node.callee.name === 'setInterval') {
// check if we are inside a function declaration or arrow function or onMounted check
let parent = path.parentPath
let insideFunction = false
while (parent) {
if (
parent.isFunctionDeclaration() ||
parent.isArrowFunctionExpression() ||
parent.isFunctionExpression() ||
parent.isObjectMethod()
) {
insideFunction = true
break
}
parent = parent.parentPath
}
if (!insideFunction) {
console.log(
`Top-level setInterval found in ${file} at line ${path.node.loc.start.line}`
)
}
}
}
})
})
+1396
View File
File diff suppressed because it is too large Load Diff
-71
View File
@@ -1,71 +0,0 @@
const fs = require('fs')
const path = require('path')
const rootDir = process.cwd()
const docsReadmeDir = path.join(rootDir, 'docs-readme')
const rootReadmePath = path.join(rootDir, 'README.md')
const locales = [
{ code: 'zh-CN', name: '简体中文' },
{ code: 'zh-TW', name: '繁體中文' },
{ code: 'en-US', name: 'English' },
{ code: 'ja-JP', name: '日本語' },
{ code: 'es-ES', name: 'Español' },
{ code: 'fr-FR', name: 'Français' },
{ code: 'tlh', name: 'Klingon' },
{ code: 'ko-KR', name: '한국어' },
{ code: 'ar-SA', name: 'العربية' },
{ code: 'tr-TR', name: 'Türkçe' },
{ code: 'vi-VN', name: 'Tiếng_Việt' },
{ code: 'de-DE', name: 'Deutsch' },
{ code: 'bn-BD', name: 'বাংলা' }
]
function generateNavBlock(isRoot) {
const links = locales.map((locale) => {
const href = isRoot
? `docs-readme/${locale.code}/README.md`
: `../${locale.code}/README.md`
return ` <a href="${href}"><img alt="${locale.name}" src="https://img.shields.io/badge/${locale.name}-d9d9d9"></a>`
})
return `<p align="center">\n${links.join('\n')}\n</p>`
}
function updateFile(filePath, isRoot) {
if (!fs.existsSync(filePath)) {
console.log(`File not found: ${filePath}`)
return
}
let content = fs.readFileSync(filePath, 'utf8')
const newNavBlock = generateNavBlock(isRoot)
// Regex to match the existing nav block. It starts with <p align="center"> and contains the badges.
// The badges in previous versions had slightly different names/links, but they all were inside <p align="center"> ... </p>
// We look for the block containing "简体中文" or "English" to be sure it's the language nav.
const regex =
/<p align="center">\s*<a href=".*README.*\.md"><img alt=".*".*>\s*<\/a>[\s\S]*?<\/p>/
if (regex.test(content)) {
content = content.replace(regex, newNavBlock)
fs.writeFileSync(filePath, content, 'utf8')
console.log(`Updated ${filePath}`)
} else {
console.log(`Nav block not found in ${filePath}`)
// Fallback: try to find where it should be (after the main title block?)
// In previous steps, we saw it's after the main title block div end or inside it.
// Let's try to match a simpler pattern if the complex one fails, or manual inspection might be needed.
// Given I just wrote them, they should match.
}
}
// Update root README
updateFile(rootReadmePath, true)
// Update docs-readme files
locales.forEach((locale) => {
const filePath = path.join(docsReadmeDir, locale.code, 'README.md')
updateFile(filePath, false)
})