docs: 重构 README 附录展示 & 新增多个附录交互组件
README 更新: - 移除顶部 header.png 横幅图片 - 新增「附录知识库」板块,以 3×3 网格展示 9 大知识领域精选内容 - 附录链接指向部署版网站 (datawhalechina.github.io) - 阶段表格新增「附录」行,突出 80+ 交互式专题 - 章节标题「新手入门 & PM」简化为「零基础入门」 - News 新增 2026-02-25 附录知识库更新条目 新增交互组件: - 异步任务队列 (async-task-queues) 演示组件 - 文件存储 (file-storage) 演示组件 - 项目架构 (project-architecture) 演示组件 - 限流与背压 (rate-limiting) 演示组件 - 搜索引擎 (search-engines) 演示组件 - 计算机基础: AppLaunch/BiosUefi/OSBoot 等启动流程演示组件 新增附录文档: - 前端项目架构 (frontend-project-architecture.md) - 后端项目架构 (backend-project-architecture.md) 内容优化: - 算法思维、数据结构、编程语言、调试艺术等多篇附录内容更新 - HTML/CSS 布局、请求旅程等前后端文档完善 - 附录索引页 (index.md) 同步更新
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
<!--
|
||||
InvertedIndexDemo.vue
|
||||
倒排索引演示:展示搜索引擎的核心数据结构
|
||||
-->
|
||||
<template>
|
||||
<div class="inverted-index-demo">
|
||||
<div class="header">
|
||||
<div class="title">倒排索引 (Inverted Index)</div>
|
||||
<div class="subtitle">输入搜索词,观察倒排索引如何工作</div>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<input
|
||||
v-model="query"
|
||||
placeholder="试试搜索:苹果、手机、水果..."
|
||||
class="search-input"
|
||||
@input="search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="index-layout">
|
||||
<div class="docs-section">
|
||||
<div class="section-title">原始文档</div>
|
||||
<div
|
||||
v-for="doc in docs"
|
||||
:key="doc.id"
|
||||
:class="['doc-card', { highlight: matchedDocs.includes(doc.id) }]"
|
||||
>
|
||||
<span class="doc-id">Doc {{ doc.id }}</span>
|
||||
<span class="doc-text">{{ doc.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="index-section">
|
||||
<div class="section-title">倒排索引表</div>
|
||||
<div class="index-table">
|
||||
<div
|
||||
v-for="(entry, word) in invertedIndex"
|
||||
:key="word"
|
||||
:class="['index-row', { highlight: matchedWords.includes(word) }]"
|
||||
>
|
||||
<span class="index-word">{{ word }}</span>
|
||||
<span class="index-arrow">→</span>
|
||||
<span class="index-docs">
|
||||
<span v-for="id in entry" :key="id" class="doc-ref">[{{ id }}]</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="query && matchedDocs.length > 0" class="result">
|
||||
命中文档:{{ matchedDocs.map(id => 'Doc ' + id).join('、') }}
|
||||
</div>
|
||||
<div v-else-if="query" class="result no-match">
|
||||
未找到匹配文档
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const query = ref('')
|
||||
const matchedDocs = ref([])
|
||||
const matchedWords = ref([])
|
||||
|
||||
const docs = [
|
||||
{ id: 1, text: '苹果是一种常见的水果' },
|
||||
{ id: 2, text: '苹果公司发布了新款手机' },
|
||||
{ id: 3, text: '我喜欢吃水果和蔬菜' },
|
||||
{ id: 4, text: '这款手机的价格很实惠' },
|
||||
{ id: 5, text: '水果店里有苹果和香蕉' }
|
||||
]
|
||||
|
||||
const invertedIndex = {
|
||||
'苹果': [1, 2, 5],
|
||||
'水果': [1, 3, 5],
|
||||
'手机': [2, 4],
|
||||
'公司': [2],
|
||||
'发布': [2],
|
||||
'喜欢': [3],
|
||||
'蔬菜': [3],
|
||||
'价格': [4],
|
||||
'实惠': [4],
|
||||
'香蕉': [5],
|
||||
'常见': [1]
|
||||
}
|
||||
|
||||
function search() {
|
||||
const q = query.value.trim()
|
||||
if (!q) {
|
||||
matchedDocs.value = []
|
||||
matchedWords.value = []
|
||||
return
|
||||
}
|
||||
const words = Object.keys(invertedIndex).filter(w => q.includes(w))
|
||||
matchedWords.value = words
|
||||
const docSet = new Set()
|
||||
words.forEach(w => invertedIndex[w].forEach(id => docSet.add(id)))
|
||||
matchedDocs.value = [...docSet].sort()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inverted-index-demo {
|
||||
border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0;
|
||||
}
|
||||
.header { margin-bottom: 1rem; }
|
||||
.title { font-weight: 700; font-size: 1.1rem; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
|
||||
.search-box { margin-bottom: 1rem; }
|
||||
.search-input {
|
||||
width: 100%; padding: 0.6rem 0.75rem; border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg);
|
||||
font-size: 0.9rem; outline: none;
|
||||
}
|
||||
.search-input:focus { border-color: var(--vp-c-brand); }
|
||||
.index-layout { display: flex; gap: 1rem; margin-bottom: 1rem; }
|
||||
.docs-section, .index-section { flex: 1; }
|
||||
.section-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.5rem; }
|
||||
.doc-card {
|
||||
display: flex; gap: 0.5rem; padding: 0.4rem 0.6rem; margin-bottom: 0.25rem;
|
||||
border-radius: 6px; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
font-size: 0.8rem; transition: all 0.2s;
|
||||
}
|
||||
.doc-card.highlight { border-color: var(--vp-c-brand); background: rgba(var(--vp-c-brand-rgb), 0.05); }
|
||||
.doc-id { font-weight: 700; color: var(--vp-c-brand); white-space: nowrap; }
|
||||
.index-row {
|
||||
display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.5rem;
|
||||
margin-bottom: 0.2rem; border-radius: 4px; font-size: 0.8rem;
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
.index-row.highlight { border-color: #22c55e; background: rgba(34,197,94,0.05); }
|
||||
.index-word { font-weight: 700; min-width: 40px; }
|
||||
.index-arrow { color: var(--vp-c-text-3); }
|
||||
.doc-ref {
|
||||
padding: 0.1rem 0.3rem; background: var(--vp-c-bg-soft); border-radius: 3px;
|
||||
font-family: var(--vp-font-family-mono); font-size: 0.75rem; margin-right: 0.2rem;
|
||||
}
|
||||
.result { padding: 0.5rem 0.75rem; border-radius: 6px; font-size: 0.85rem; background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.3); }
|
||||
.result.no-match { background: rgba(245,158,11,0.08); border-color: rgba(245,158,11,0.3); }
|
||||
@media (max-width: 640px) { .index-layout { flex-direction: column; } }
|
||||
</style>
|
||||
@@ -0,0 +1,126 @@
|
||||
<!--
|
||||
SearchRelevanceDemo.vue
|
||||
搜索相关性评分演示:展示 TF-IDF 和 BM25 评分原理
|
||||
-->
|
||||
<template>
|
||||
<div class="relevance-demo">
|
||||
<div class="header">
|
||||
<div class="title">搜索相关性评分</div>
|
||||
<div class="subtitle">输入查询词,观察不同文档的相关性得分</div>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<input v-model="query" placeholder="输入搜索词,如:数据库" class="search-input" />
|
||||
<button class="search-btn" @click="calcScores">计算得分</button>
|
||||
</div>
|
||||
|
||||
<div v-if="results.length > 0" class="results">
|
||||
<div
|
||||
v-for="(r, i) in results"
|
||||
:key="i"
|
||||
class="result-item"
|
||||
>
|
||||
<div class="result-rank">#{{ i + 1 }}</div>
|
||||
<div class="result-content">
|
||||
<div class="result-title">{{ r.title }}</div>
|
||||
<div class="result-snippet">{{ r.snippet }}</div>
|
||||
</div>
|
||||
<div class="result-score">
|
||||
<div class="score-bar">
|
||||
<div class="score-fill" :style="{ width: r.scorePercent + '%' }"></div>
|
||||
</div>
|
||||
<div class="score-value">{{ r.score.toFixed(2) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scoring-info">
|
||||
<div class="info-title">BM25 评分因子</div>
|
||||
<div class="factor-grid">
|
||||
<div class="factor">
|
||||
<div class="factor-name">词频 (TF)</div>
|
||||
<div class="factor-desc">关键词在文档中出现的次数越多,得分越高(但有上限)</div>
|
||||
</div>
|
||||
<div class="factor">
|
||||
<div class="factor-name">逆文档频率 (IDF)</div>
|
||||
<div class="factor-desc">越稀有的词权重越高,"的"这种常见词权重很低</div>
|
||||
</div>
|
||||
<div class="factor">
|
||||
<div class="factor-name">文档长度</div>
|
||||
<div class="factor-desc">较短文档中出现关键词,比长文档中出现更有意义</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const query = ref('')
|
||||
const results = ref([])
|
||||
|
||||
const documents = [
|
||||
{ title: 'MySQL 数据库入门', snippet: '数据库是存储和管理数据的系统,MySQL 是最流行的关系型数据库之一', keywords: { '数据库': 3, '数据': 2, 'MySQL': 2, '存储': 1 } },
|
||||
{ title: 'Redis 缓存设计', snippet: 'Redis 是内存数据库,常用作缓存层,提升数据读取性能', keywords: { 'Redis': 2, '缓存': 2, '数据库': 1, '数据': 1, '性能': 1 } },
|
||||
{ title: 'Python 数据分析', snippet: '使用 Python 进行数据清洗、分析和可视化', keywords: { 'Python': 2, '数据': 3, '分析': 2, '可视化': 1 } },
|
||||
{ title: '分布式数据库架构', snippet: '分布式数据库通过分片和复制实现高可用和水平扩展', keywords: { '分布式': 2, '数据库': 2, '分片': 1, '高可用': 1 } },
|
||||
{ title: 'API 接口设计', snippet: 'RESTful API 设计规范与最佳实践', keywords: { 'API': 3, '设计': 2, 'RESTful': 1 } }
|
||||
]
|
||||
|
||||
function calcScores() {
|
||||
if (!query.value.trim()) { results.value = []; return }
|
||||
const q = query.value.trim()
|
||||
const scored = documents.map(doc => {
|
||||
let score = 0
|
||||
for (const [word, tf] of Object.entries(doc.keywords)) {
|
||||
if (word.includes(q) || q.includes(word)) {
|
||||
const idf = Math.log(documents.length / (1 + documents.filter(d => d.keywords[word]).length))
|
||||
score += tf * (idf + 1)
|
||||
}
|
||||
}
|
||||
return { ...doc, score }
|
||||
}).filter(d => d.score > 0).sort((a, b) => b.score - a.score)
|
||||
|
||||
const maxScore = scored.length > 0 ? scored[0].score : 1
|
||||
results.value = scored.map(r => ({ ...r, scorePercent: (r.score / maxScore) * 100 }))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.relevance-demo {
|
||||
border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0;
|
||||
}
|
||||
.header { margin-bottom: 1rem; }
|
||||
.title { font-weight: 700; font-size: 1.1rem; }
|
||||
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
|
||||
.search-box { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
|
||||
.search-input {
|
||||
flex: 1; padding: 0.5rem 0.75rem; border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); font-size: 0.9rem;
|
||||
}
|
||||
.search-btn {
|
||||
padding: 0.5rem 1rem; border-radius: 6px; border: none;
|
||||
background: var(--vp-c-brand); color: #fff; cursor: pointer; font-size: 0.85rem;
|
||||
}
|
||||
.results { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem; }
|
||||
.result-item {
|
||||
display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem;
|
||||
border-radius: 8px; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
.result-rank { font-weight: 700; font-size: 1rem; color: var(--vp-c-brand); min-width: 30px; }
|
||||
.result-content { flex: 1; }
|
||||
.result-title { font-weight: 600; font-size: 0.9rem; }
|
||||
.result-snippet { font-size: 0.8rem; color: var(--vp-c-text-2); }
|
||||
.result-score { min-width: 120px; }
|
||||
.score-bar { height: 8px; background: var(--vp-c-bg-soft); border-radius: 4px; overflow: hidden; }
|
||||
.score-fill { height: 100%; background: var(--vp-c-brand); border-radius: 4px; transition: width 0.3s; }
|
||||
.score-value { font-size: 0.75rem; color: var(--vp-c-text-3); text-align: right; font-family: var(--vp-font-family-mono); }
|
||||
.scoring-info { padding: 0.75rem; border-radius: 8px; background: rgba(var(--vp-c-brand-rgb),0.05); border: 1px solid var(--vp-c-brand); }
|
||||
.info-title { font-weight: 700; font-size: 0.9rem; margin-bottom: 0.5rem; }
|
||||
.factor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.5rem; }
|
||||
.factor { padding: 0.5rem; background: var(--vp-c-bg); border-radius: 6px; }
|
||||
.factor-name { font-weight: 600; font-size: 0.85rem; margin-bottom: 0.2rem; }
|
||||
.factor-desc { font-size: 0.75rem; color: var(--vp-c-text-2); line-height: 1.5; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user