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:
+551
@@ -0,0 +1,551 @@
|
||||
<template>
|
||||
<div class="architecture-comparison-demo">
|
||||
<div class="demo-header">
|
||||
<span class="icon">🏗️</span>
|
||||
<span class="title">前后端项目架构对比</span>
|
||||
<span class="subtitle">点击切换查看不同架构层次</span>
|
||||
</div>
|
||||
|
||||
<!-- 切换按钮 -->
|
||||
<div class="toggle-buttons">
|
||||
<button
|
||||
:class="['toggle-btn', { active: activeType === 'frontend' }]"
|
||||
@click="activeType = 'frontend'"
|
||||
>
|
||||
<span class="btn-icon">🎨</span>
|
||||
前端架构
|
||||
</button>
|
||||
<button
|
||||
:class="['toggle-btn', { active: activeType === 'backend' }]"
|
||||
@click="activeType = 'backend'"
|
||||
>
|
||||
<span class="btn-icon">⚙️</span>
|
||||
后端架构
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 架构展示 -->
|
||||
<div class="architecture-display">
|
||||
<!-- 前端架构 -->
|
||||
<div v-if="activeType === 'frontend'" class="architecture-layers">
|
||||
<div
|
||||
v-for="(layer, index) in frontendLayers"
|
||||
:key="layer.id"
|
||||
class="layer-box"
|
||||
:class="[layer.class, { active: activeLayer === layer.id }]"
|
||||
:style="{ animationDelay: `${index * 0.1}s` }"
|
||||
@click="setActiveLayer(layer.id)"
|
||||
>
|
||||
<div class="layer-header">
|
||||
<span class="layer-icon">{{ layer.icon }}</span>
|
||||
<span class="layer-name">{{ layer.name }}</span>
|
||||
<span class="layer-badge">{{ layer.badge }}</span>
|
||||
</div>
|
||||
<div class="layer-content">
|
||||
<div class="duty">{{ layer.duty }}</div>
|
||||
<div class="example">🌰 {{ layer.example }}</div>
|
||||
</div>
|
||||
<div v-if="index < frontendLayers.length - 1" class="layer-arrow">
|
||||
<span class="arrow-icon">↓</span>
|
||||
<span class="arrow-text">{{ layer.arrow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 后端架构 -->
|
||||
<div v-else class="architecture-layers">
|
||||
<div
|
||||
v-for="(layer, index) in backendLayers"
|
||||
:key="layer.id"
|
||||
class="layer-box"
|
||||
:class="[layer.class, { active: activeLayer === layer.id }]"
|
||||
:style="{ animationDelay: `${index * 0.1}s` }"
|
||||
@click="setActiveLayer(layer.id)"
|
||||
>
|
||||
<div class="layer-header">
|
||||
<span class="layer-icon">{{ layer.icon }}</span>
|
||||
<span class="layer-name">{{ layer.name }}</span>
|
||||
<span class="layer-badge">{{ layer.badge }}</span>
|
||||
</div>
|
||||
<div class="layer-content">
|
||||
<div class="duty">{{ layer.duty }}</div>
|
||||
<div class="example">🌰 {{ layer.example }}</div>
|
||||
</div>
|
||||
<div v-if="index < backendLayers.length - 1" class="layer-arrow">
|
||||
<span class="arrow-icon">↓</span>
|
||||
<span class="arrow-text">{{ layer.arrow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情面板 -->
|
||||
<Transition name="slide">
|
||||
<div v-if="currentLayer" class="detail-panel">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ currentLayer.icon }}</span>
|
||||
<span class="detail-title">{{ currentLayer.name }}</span>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-section">
|
||||
<div class="section-title">📁 典型文件</div>
|
||||
<div class="file-list">
|
||||
<code v-for="file in currentLayer.files" :key="file" class="file-tag">{{ file }}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<div class="section-title">✅ 设计原则</div>
|
||||
<ul class="principle-list">
|
||||
<li v-for="principle in currentLayer.principles" :key="principle">{{ principle }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>好的架构就像整理好的空间——前端像衣柜(按功能分类展示),后端像厨房(按流程分工协作)。点击上方层次查看详情!
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activeType = ref('frontend')
|
||||
const activeLayer = ref(null)
|
||||
|
||||
const frontendLayers = [
|
||||
{
|
||||
id: 'views',
|
||||
name: 'Views / Pages',
|
||||
icon: '📄',
|
||||
badge: '页面层',
|
||||
class: 'views-layer',
|
||||
duty: '职责:页面组件,对应路由',
|
||||
example: 'Home.vue、UserProfile.vue',
|
||||
arrow: '组合',
|
||||
files: ['Home/index.vue', 'User/Profile.vue', 'pages/about.tsx'],
|
||||
principles: ['保持"薄",逻辑下沉到 hooks', '页面级状态管理', '路由懒加载']
|
||||
},
|
||||
{
|
||||
id: 'components',
|
||||
name: 'Components',
|
||||
icon: '🧩',
|
||||
badge: '组件层',
|
||||
class: 'components-layer',
|
||||
duty: '职责:可复用的 UI 组件',
|
||||
example: 'Button.vue、Modal.vue、UserCard.vue',
|
||||
arrow: '调用',
|
||||
files: ['common/Button/', 'business/UserCard/', 'layout/Header/'],
|
||||
principles: ['单一职责,一个组件只做一件事', 'Props 清晰可预测', '样式隔离(scoped/css-modules)']
|
||||
},
|
||||
{
|
||||
id: 'hooks',
|
||||
name: 'Hooks / Composables',
|
||||
icon: '🎣',
|
||||
badge: '逻辑层',
|
||||
class: 'hooks-layer',
|
||||
duty: '职责:可复用的业务逻辑',
|
||||
example: 'useAuth()、useLoading()、useForm()',
|
||||
arrow: '使用',
|
||||
files: ['useAuth.js', 'usePagination.ts', 'composables/useFetch.js'],
|
||||
principles: ['纯函数优先', '单一功能,便于测试', '命名以 use 开头']
|
||||
},
|
||||
{
|
||||
id: 'services',
|
||||
name: 'Services / API',
|
||||
icon: '🌐',
|
||||
badge: '服务层',
|
||||
class: 'services-layer',
|
||||
duty: '职责:API 调用,数据获取',
|
||||
example: 'userApi.getProfile()、orderApi.create()',
|
||||
arrow: '请求',
|
||||
files: ['services/user.js', 'api/request.ts', 'clients/http.js'],
|
||||
principles: ['统一错误处理', '请求/响应拦截', '接口统一管理']
|
||||
},
|
||||
{
|
||||
id: 'utils',
|
||||
name: 'Utils / Helpers',
|
||||
icon: '🛠️',
|
||||
badge: '工具层',
|
||||
class: 'utils-layer',
|
||||
duty: '职责:通用工具函数',
|
||||
example: 'formatDate()、storage.set()、validator.email()',
|
||||
arrow: '',
|
||||
files: ['utils/format.js', 'helpers/storage.ts', 'lib/validator.js'],
|
||||
principles: ['纯函数,无副作用', '单一职责', '完善的 JSDoc 注释']
|
||||
}
|
||||
]
|
||||
|
||||
const backendLayers = [
|
||||
{
|
||||
id: 'controller',
|
||||
name: 'Controller',
|
||||
icon: '🎮',
|
||||
badge: '入口层',
|
||||
class: 'controller-layer',
|
||||
duty: '职责:接收 HTTP 请求,返回响应',
|
||||
example: 'UserController.getById()、OrderController.create()',
|
||||
arrow: '调用',
|
||||
files: ['userController.js', 'routes/api.js', 'handlers/order.ts'],
|
||||
principles: ['只处理 HTTP 相关逻辑', '参数校验', '不直接操作数据库']
|
||||
},
|
||||
{
|
||||
id: 'service',
|
||||
name: 'Service',
|
||||
icon: '⚙️',
|
||||
badge: '业务层',
|
||||
class: 'service-layer',
|
||||
duty: '职责:核心业务逻辑,事务管理',
|
||||
example: 'UserService.createUser()、OrderService.process()',
|
||||
arrow: '调用',
|
||||
files: ['userService.js', 'services/order.ts', 'business/user.js'],
|
||||
principles: ['包含核心业务规则', '协调多个 Repository', '管理事务边界']
|
||||
},
|
||||
{
|
||||
id: 'repository',
|
||||
name: 'Repository',
|
||||
icon: '🗄️',
|
||||
badge: '数据层',
|
||||
class: 'repository-layer',
|
||||
duty: '职责:数据持久化,数据库操作',
|
||||
example: 'UserRepository.findById()、OrderRepository.save()',
|
||||
arrow: '查询',
|
||||
files: ['userRepository.js', 'dao/order.ts', 'models/user.js'],
|
||||
principles: ['只负责数据存取', 'ORM 封装', '不包含业务逻辑']
|
||||
},
|
||||
{
|
||||
id: 'model',
|
||||
name: 'Model / Entity',
|
||||
icon: '📊',
|
||||
badge: '模型层',
|
||||
class: 'model-layer',
|
||||
duty: '职责:数据结构和业务规则定义',
|
||||
example: 'User 类、Order 实体、DTO 定义',
|
||||
arrow: '',
|
||||
files: ['models/User.js', 'entities/order.ts', 'dto/userDto.js'],
|
||||
principles: ['定义数据结构', '字段验证规则', '与其他层解耦']
|
||||
}
|
||||
]
|
||||
|
||||
const currentLayer = computed(() => {
|
||||
const layers = activeType.value === 'frontend' ? frontendLayers : backendLayers
|
||||
return layers.find(l => l.id === activeLayer.value)
|
||||
})
|
||||
|
||||
function setActiveLayer(id) {
|
||||
activeLayer.value = activeLayer.value === id ? null : id
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.architecture-comparison-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 700px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
/* 切换按钮 */
|
||||
.toggle-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* 架构层 */
|
||||
.architecture-layers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.layer-box {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
animation: fadeInUp 0.3s ease forwards;
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-box:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.layer-box.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
/* 不同层的颜色 */
|
||||
.views-layer {
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.components-layer {
|
||||
border-left: 4px solid #2ecc71;
|
||||
}
|
||||
|
||||
.hooks-layer {
|
||||
border-left: 4px solid #9b59b6;
|
||||
}
|
||||
|
||||
.services-layer {
|
||||
border-left: 4px solid #e67e22;
|
||||
}
|
||||
|
||||
.utils-layer {
|
||||
border-left: 4px solid #95a5a6;
|
||||
}
|
||||
|
||||
.controller-layer {
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.service-layer {
|
||||
border-left: 4px solid #2ecc71;
|
||||
}
|
||||
|
||||
.repository-layer {
|
||||
border-left: 4px solid #e67e22;
|
||||
}
|
||||
|
||||
.model-layer {
|
||||
border-left: 4px solid #9b59b6;
|
||||
}
|
||||
|
||||
.layer-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.layer-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.layer-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.95rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.layer-badge {
|
||||
margin-left: auto;
|
||||
padding: 0.15rem 0.4rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.layer-content {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.duty {
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.example {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.layer-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 详情面板 */
|
||||
.detail-panel {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.file-tag {
|
||||
padding: 0.2rem 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.principle-list {
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.principle-list li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* 信息框 */
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.info-box .icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 640px) {
|
||||
.toggle-btn {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.layer-name {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.duty, .example {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user