feat: comprehensive documentation and demo updates

- Update READMEs and docs across multiple languages
- Enhance interactive demos for Agent, LLM, VLM, Audio, Image Gen, Terminal, and Web Basics
- Add new appendix sections for Database and IDE intros
- Update VitePress config, theme, and utility scripts
- Clean up unused assets and components
This commit is contained in:
sanbuphy
2026-01-16 19:10:21 +08:00
parent c8567ce23f
commit 73f4788d7e
150 changed files with 19530 additions and 13401 deletions
@@ -0,0 +1,263 @@
<script setup>
import { ref, computed } from 'vue'
const searchQuery = ref(55)
const isSearching = ref(false)
const scanCurrentIndex = ref(-1)
const treeActiveNodes = ref([])
const searchResult = ref(null)
const mode = ref('scan') // 'scan' or 'index'
const DATA_SIZE = 64
const data = Array.from({ length: DATA_SIZE }, (_, i) => ({
id: i + 1,
value: `Data-${i + 1}`
}))
// Simplified Tree Search Simulation (Binary Search steps)
const startSearch = async () => {
if (isSearching.value) return
isSearching.value = true
scanCurrentIndex.value = -1
treeActiveNodes.value = []
searchResult.value = null
const target = Number(searchQuery.value)
if (mode.value === 'scan') {
for (let i = 0; i < data.length; i++) {
scanCurrentIndex.value = i
await new Promise((r) => setTimeout(r, 30)) // 30ms per step
if (data[i].id === target) {
searchResult.value = data[i]
break
}
}
} else {
// Tree Search Simulation (Binary Search steps)
let start = 0
let end = data.length - 1
while (start <= end) {
let mid = Math.floor((start + end) / 2)
treeActiveNodes.value.push(mid) // Highlight the "node" we are checking
await new Promise((r) => setTimeout(r, 400)) // Slower steps for tree to be visible
if (data[mid].id === target) {
searchResult.value = data[mid]
break
} else if (data[mid].id < target) {
start = mid + 1
} else {
end = mid - 1
}
}
}
isSearching.value = false
}
</script>
<template>
<div class="db-demo">
<div class="controls">
<div class="control-item">
<span>查找 ID: </span>
<el-input-number
v-model="searchQuery"
:min="1"
:max="DATA_SIZE"
size="small"
/>
</div>
<el-radio-group v-model="mode" size="small">
<el-radio-button label="scan">全表扫描 (O(n))</el-radio-button>
<el-radio-button label="index">索引查找 (O(log n))</el-radio-button>
</el-radio-group>
<el-button
type="primary"
@click="startSearch"
:loading="isSearching"
size="small"
>开始查询</el-button
>
</div>
<div class="visualization-area">
<!-- Full Scan Visualization -->
<div v-if="mode === 'scan'" class="view-container">
<div class="grid">
<div
v-for="(item, index) in data"
:key="item.id"
class="data-block"
:class="{
active: index === scanCurrentIndex,
found: searchResult && searchResult.id === item.id
}"
>
{{ item.id }}
</div>
</div>
<p class="desc">
全表扫描数据库必须逐行检查数据直到找到匹配项数据越多速度越慢
</p>
</div>
<!-- Index Visualization -->
<div v-else class="view-container">
<div class="grid">
<div
v-for="(item, index) in data"
:key="item.id"
class="data-block tree-node"
:class="{
visited: treeActiveNodes.includes(index),
found: searchResult && searchResult.id === item.id,
dimmed:
treeActiveNodes.length > 0 && !treeActiveNodes.includes(index)
}"
>
{{ item.id }}
</div>
</div>
<p class="desc">
索引查找类似二分查找或 B+
每次比较都能排除掉一半或更多的数据极快地定位目标
</p>
</div>
</div>
<div class="stats" v-if="!isSearching && searchResult">
<div class="stat-item">
<span class="label">查找结果:</span>
<span class="value">{{ searchResult.value }}</span>
</div>
<div class="stat-item">
<span class="label">操作次数:</span>
<span class="value highlight"
>{{
mode === 'scan' ? scanCurrentIndex + 1 : treeActiveNodes.length
}}
</span
>
</div>
</div>
</div>
</template>
<style scoped>
.db-demo {
padding: 20px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #ffffff;
font-family: sans-serif;
}
.controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.control-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.view-container {
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
}
.grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: center;
margin-bottom: 15px;
}
.data-block {
width: 32px;
height: 32px;
background: #f0f2f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
border-radius: 4px;
color: #606266;
transition: all 0.3s;
border: 1px solid transparent;
}
.data-block.active {
background: #409eff;
color: white;
transform: scale(1.15);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-color: #409eff;
z-index: 1;
}
.data-block.found {
background: #67c23a;
color: white;
transform: scale(1.2);
box-shadow: 0 0 15px rgba(103, 194, 58, 0.5);
border-color: #67c23a;
z-index: 2;
font-weight: bold;
}
.tree-node.visited {
background: #e6a23c;
color: white;
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(230, 162, 60, 0.4);
z-index: 1;
}
.tree-node.dimmed {
opacity: 0.2;
filter: grayscale(100%);
}
.desc {
font-size: 14px;
color: #909399;
text-align: center;
margin-top: 10px;
max-width: 600px;
}
.stats {
margin-top: 20px;
padding: 15px;
background: #fdf6ec;
border-radius: 4px;
display: flex;
justify-content: space-around;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.label {
font-size: 12px;
color: #909399;
}
.value {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.value.highlight {
color: #e6a23c;
font-size: 20px;
}
</style>
@@ -0,0 +1,312 @@
<script setup>
import { ref } from 'vue'
const activeTab = ref('excel') // 'excel' or 'db'
// Excel Data (Flat, Redundant)
const excelData = [
{
id: 1,
date: '2023-10-01',
book: 'AI 入门',
price: 59,
user: '张三',
phone: '13800138000'
},
{
id: 2,
date: '2023-10-02',
book: 'Python 编程',
price: 89,
user: '李四',
phone: '13900139000'
},
{
id: 3,
date: '2023-10-03',
book: '算法导论',
price: 120,
user: '张三',
phone: '13800138000'
},
{
id: 4,
date: '2023-10-03',
book: '数据库原理',
price: 45,
user: '王五',
phone: '13700137000'
},
{
id: 5,
date: '2023-10-04',
book: 'Vue.js 实战',
price: 78,
user: '张三',
phone: '13800138000'
}
]
// DB Data (Normalized)
const usersTable = [
{ id: 101, name: '张三', phone: '13800138000' },
{ id: 102, name: '李四', phone: '13900139000' },
{ id: 103, name: '王五', phone: '13700137000' }
]
const ordersTable = [
{ id: 1, date: '2023-10-01', book: 'AI 入门', price: 59, user_id: 101 },
{ id: 2, date: '2023-10-02', book: 'Python 编程', price: 89, user_id: 102 },
{ id: 3, date: '2023-10-03', book: '算法导论', price: 120, user_id: 101 },
{ id: 4, date: '2023-10-03', book: '数据库原理', price: 45, user_id: 103 },
{ id: 5, date: '2023-10-04', book: 'Vue.js 实战', price: 78, user_id: 101 }
]
const hoveredUserId = ref(null)
const setHover = (id) => {
hoveredUserId.value = id
}
</script>
<template>
<div class="relational-demo">
<div class="tabs">
<div
class="tab"
:class="{ active: activeTab === 'excel' }"
@click="activeTab = 'excel'"
>
Excel 模式 (单表)
</div>
<div
class="tab"
:class="{ active: activeTab === 'db' }"
@click="activeTab = 'db'"
>
数据库模式 (多表关联)
</div>
</div>
<div class="content-area">
<!-- Excel Mode -->
<div v-if="activeTab === 'excel'" class="excel-view">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>订单号</th>
<th>日期</th>
<th>书名</th>
<th>价格</th>
<th class="highlight-col">购买者</th>
<th class="highlight-col">电话</th>
</tr>
</thead>
<tbody>
<tr v-for="row in excelData" :key="row.id">
<td>{{ row.id }}</td>
<td>{{ row.date }}</td>
<td>{{ row.book }}</td>
<td>{{ row.price }}</td>
<td class="highlight-cell">{{ row.user }}</td>
<td class="highlight-cell">{{ row.phone }}</td>
</tr>
</tbody>
</table>
</div>
<div class="note error">
<p> <strong>问题</strong> "张三"的信息重复存储了 3 </p>
<p>如果张三换了电话你需要修改 3 行数据很容易漏改</p>
</div>
</div>
<!-- DB Mode -->
<div v-else class="db-view">
<div class="db-layout">
<!-- Users Table -->
<div class="db-table users-table">
<div class="table-title">用户表 (Users)</div>
<table>
<thead>
<tr>
<th>ID (主键)</th>
<th>姓名</th>
<th>电话</th>
</tr>
</thead>
<tbody>
<tr
v-for="u in usersTable"
:key="u.id"
:class="{ active: hoveredUserId === u.id }"
@mouseenter="setHover(u.id)"
@mouseleave="setHover(null)"
>
<td>{{ u.id }}</td>
<td>{{ u.name }}</td>
<td>{{ u.phone }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Connection Lines (Visual only, simplified) -->
<div class="connector">
<div class="arrow"> 关联 (Join) </div>
</div>
<!-- Orders Table -->
<div class="db-table orders-table">
<div class="table-title">订单表 (Orders)</div>
<table>
<thead>
<tr>
<th>订单号</th>
<th>书名</th>
<th>价格</th>
<th class="highlight-col">用户 ID (外键)</th>
</tr>
</thead>
<tbody>
<tr
v-for="o in ordersTable"
:key="o.id"
:class="{ active: hoveredUserId === o.user_id }"
@mouseenter="setHover(o.user_id)"
@mouseleave="setHover(null)"
>
<td>{{ o.id }}</td>
<td>{{ o.book }}</td>
<td>{{ o.price }}</td>
<td class="highlight-cell">{{ o.user_id }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="note success">
<p> <strong>优势</strong> 订单表只存 "用户 ID"</p>
<p>
鼠标悬停在某一行看看它们是如何自动关联的修改用户表一次所有订单都会自动更新
</p>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.relational-demo {
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #fff;
overflow: hidden;
margin: 20px 0;
font-family: sans-serif;
}
.tabs {
display: flex;
background: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
}
.tab {
padding: 12px 24px;
cursor: pointer;
font-size: 14px;
color: #606266;
border-right: 1px solid #e4e7ed;
transition: all 0.2s;
}
.tab.active {
background: #fff;
color: #409eff;
font-weight: bold;
border-bottom: 2px solid #409eff;
margin-bottom: -1px;
}
.content-area {
padding: 20px;
}
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th,
td {
border: 1px solid #ebeef5;
padding: 8px 12px;
text-align: left;
}
th {
background: #fafafa;
font-weight: bold;
color: #303133;
}
.highlight-col {
background: #ecf5ff;
color: #409eff;
}
.highlight-cell {
background: #f0f9eb;
color: #67c23a;
font-weight: bold;
}
.excel-view .highlight-cell {
background: #fef0f0;
color: #f56c6c;
}
.db-layout {
display: flex;
gap: 20px;
align-items: flex-start;
flex-wrap: wrap;
}
.db-table {
flex: 1;
min-width: 280px;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
.table-title {
background: #f2f6fc;
padding: 8px 12px;
font-weight: bold;
font-size: 13px;
color: #606266;
border-bottom: 1px solid #ebeef5;
}
.connector {
display: flex;
align-items: center;
justify-content: center;
padding-top: 50px;
font-size: 12px;
color: #909399;
}
tr.active {
background: #ecf5ff;
}
.note {
margin-top: 15px;
padding: 12px;
border-radius: 4px;
font-size: 13px;
line-height: 1.6;
}
.note.error {
background: #fef0f0;
color: #f56c6c;
}
.note.success {
background: #f0f9eb;
color: #67c23a;
}
</style>
@@ -0,0 +1,302 @@
<script setup>
import { ref, computed } from 'vue'
const users = ref([
{ id: 101, name: '张三', score: 100 },
{ id: 102, name: '李四', score: 85 },
{ id: 103, name: '王五', score: 120 },
{ id: 104, name: '赵六', score: 90 }
])
const commands = [
{
label: '查询所有用户',
sql: 'SELECT * FROM users;',
action: 'read_all'
},
{
label: '查询分数 > 90',
sql: 'SELECT * FROM users WHERE score > 90;',
action: 'read_filter'
},
{
label: '给张三加 10 分',
sql: 'UPDATE users SET score = score + 10 WHERE name = "张三";',
action: 'update_add'
}
]
const currentSql = ref('')
const terminalOutput = ref([])
const displayedUsers = ref([...users.value])
const isAnimating = ref(false)
const execute = async (cmd) => {
if (isAnimating.value) return
isAnimating.value = true
currentSql.value = cmd.sql
// Reset display for read operations
if (cmd.action.startsWith('read')) {
displayedUsers.value = [] // clear first
await new Promise((r) => setTimeout(r, 300))
}
terminalOutput.value.push({ type: 'cmd', text: `> ${cmd.sql}` })
await new Promise((r) => setTimeout(r, 500))
if (cmd.action === 'read_all') {
displayedUsers.value = [...users.value]
terminalOutput.value.push({
type: 'result',
text: `Returned ${users.value.length} rows.`
})
} else if (cmd.action === 'read_filter') {
displayedUsers.value = users.value.filter((u) => u.score > 90)
terminalOutput.value.push({
type: 'result',
text: `Returned ${displayedUsers.value.length} rows.`
})
} else if (cmd.action === 'update_add') {
const target = users.value.find((u) => u.name === '张三')
if (target) {
target.score += 10
displayedUsers.value = [...users.value] // refresh display
terminalOutput.value.push({ type: 'success', text: `Updated 1 row.` })
}
}
// Scroll terminal to bottom
setTimeout(() => {
const term = document.querySelector('.sql-terminal-body')
if (term) term.scrollTop = term.scrollHeight
}, 100)
isAnimating.value = false
}
</script>
<template>
<div class="sql-playground">
<div class="left-panel">
<div class="panel-header">数据库表: users</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>score</th>
</tr>
</thead>
<tbody>
<tr v-for="u in displayedUsers" :key="u.id" class="fade-in">
<td>{{ u.id }}</td>
<td>{{ u.name }}</td>
<td>
<span
:class="{ 'score-up': u.name === '张三' && u.score > 100 }"
>{{ u.score }}</span
>
</td>
</tr>
<tr v-if="displayedUsers.length === 0">
<td colspan="3" class="empty-hint">无数据或正在查询...</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="right-panel">
<div class="panel-header">SQL 终端</div>
<div class="sql-terminal-body">
<div v-for="(line, i) in terminalOutput" :key="i" :class="line.type">
{{ line.text }}
</div>
<div class="cursor-line">_</div>
</div>
<div class="actions">
<div class="action-label">常用指令:</div>
<div class="btn-group">
<button
v-for="cmd in commands"
:key="cmd.label"
@click="execute(cmd)"
:disabled="isAnimating"
class="cmd-btn"
>
{{ cmd.label }}
</button>
</div>
<div class="current-sql" v-if="currentSql">
执行中: <code>{{ currentSql }}</code>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.sql-playground {
display: flex;
border: 1px solid #333;
border-radius: 8px;
background: #1e1e1e;
color: #ccc;
font-family: 'Consolas', monospace;
overflow: hidden;
min-height: 350px;
}
.left-panel {
flex: 1;
border-right: 1px solid #444;
display: flex;
flex-direction: column;
}
.right-panel {
flex: 1.2;
display: flex;
flex-direction: column;
background: #252526;
}
.panel-header {
background: #333;
padding: 8px 15px;
font-size: 12px;
font-weight: bold;
color: #fff;
border-bottom: 1px solid #444;
}
.table-container {
padding: 15px;
flex: 1;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th,
td {
border: 1px solid #444;
padding: 6px 10px;
text-align: left;
}
th {
color: #569cd6;
}
td {
color: #ce9178;
}
td:first-child {
color: #b5cea8;
} /* id color */
.sql-terminal-body {
flex: 1;
padding: 15px;
font-size: 13px;
overflow-y: auto;
max-height: 200px;
}
.cmd {
color: #fff;
margin-top: 5px;
}
.result {
color: #aaa;
margin-bottom: 5px;
}
.success {
color: #67c23a;
margin-bottom: 5px;
}
.cursor-line {
animation: blink 1s infinite;
}
.actions {
padding: 15px;
background: #2d2d2d;
border-top: 1px solid #444;
}
.action-label {
font-size: 12px;
margin-bottom: 8px;
color: #999;
}
.btn-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.cmd-btn {
background: #0e639c;
border: none;
color: white;
padding: 6px 12px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s;
}
.cmd-btn:hover {
background: #1177bb;
}
.cmd-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.current-sql {
margin-top: 10px;
font-size: 12px;
color: #888;
}
.current-sql code {
color: #dcdcaa;
background: transparent;
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.score-up {
color: #67c23a;
font-weight: bold;
animation: pulse 0.5s;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.4);
}
100% {
transform: scale(1);
}
}
.empty-hint {
text-align: center;
color: #666;
font-style: italic;
padding: 20px;
border: none;
}
</style>