feat(docs): add interactive demo components for technical appendices

Add placeholder Vue components for visualizing technical concepts across multiple domains including frontend routing, browser rendering, cache design, queue design, database principles, API design, cloud services, and backend evolution. These components provide interactive educational content for the documentation.

Update documentation structure to include new appendix sections and enhance existing content with visual components. Remove unused 'codex' dependency from package.json.
This commit is contained in:
sanbuphy
2026-02-06 03:34:50 +08:00
parent e8bba6f7c0
commit 7c70c37072
171 changed files with 69830 additions and 6689 deletions
@@ -0,0 +1,352 @@
<template>
<div class="layered-architecture-demo">
<div class="architecture-container">
<!-- 客户端 -->
<div class="client-layer">
<div class="layer-box client">
<div class="layer-icon">🌐</div>
<div class="layer-title">客户端</div>
<div class="layer-desc">Web / App / 小程序</div>
</div>
<div class="arrow-down"> HTTP/HTTPS</div>
</div>
<!-- 后端分层 -->
<div class="backend-layers">
<!-- Controller -->
<div
class="layer-box controller"
:class="{ active: activeLayer === 'controller' }"
@click="setActiveLayer('controller')"
>
<div class="layer-header">
<span class="layer-icon">🎮</span>
<span class="layer-name">Controller</span>
<span class="layer-badge">入口</span>
</div>
<div class="layer-content">
<div class="duty">职责接收请求参数校验调用 Service</div>
<div class="tech">技术Spring MVC / Gin / Echo</div>
</div>
</div>
<div class="arrow-down"> 调用</div>
<!-- Service -->
<div
class="layer-box service"
:class="{ active: activeLayer === 'service' }"
@click="setActiveLayer('service')"
>
<div class="layer-header">
<span class="layer-icon"></span>
<span class="layer-name">Service</span>
<span class="layer-badge">业务核心</span>
</div>
<div class="layer-content">
<div class="duty">职责业务逻辑编排事务管理跨模块协调</div>
<div class="tech">技术纯代码逻辑 / 无框架依赖</div>
</div>
</div>
<div class="arrow-down"> 调用</div>
<!-- Repository -->
<div
class="layer-box repository"
:class="{ active: activeLayer === 'repository' }"
@click="setActiveLayer('repository')"
>
<div class="layer-header">
<span class="layer-icon">🗄</span>
<span class="layer-name">Repository</span>
<span class="layer-badge">数据访问</span>
</div>
<div class="layer-content">
<div class="duty">职责数据持久化查询封装ORM 映射</div>
<div class="tech">技术MyBatis / GORM / Hibernate</div>
</div>
</div>
<div class="arrow-down"> SQL</div>
<!-- Domain -->
<div
class="layer-box domain"
:class="{ active: activeLayer === 'domain' }"
@click="setActiveLayer('domain')"
>
<div class="layer-header">
<span class="layer-icon">📦</span>
<span class="layer-name">Domain / Model</span>
<span class="layer-badge">领域模型</span>
</div>
<div class="layer-content">
<div class="duty">职责实体定义业务规则值对象</div>
<div class="tech">技术POJO / Struct / Class</div>
</div>
</div>
<div class="arrow-down"> 持久化</div>
<!-- 数据库 -->
<div class="layer-box database">
<div class="layer-icon">💾</div>
<div class="layer-title">数据库</div>
<div class="layer-desc">MySQL / PostgreSQL / MongoDB</div>
</div>
</div>
<!-- 右侧说明面板 -->
<div class="info-panel" v-if="activeLayer">
<h4>{{ layerInfo.title }}</h4>
<p>{{ layerInfo.description }}</p>
<div class="analogy">
<strong>💡 类比</strong>{{ layerInfo.analogy }}
</div>
<div class="common-mistakes">
<strong> 常见错误</strong>
<ul>
<li v-for="mistake in layerInfo.mistakes" :key="mistake">{{ mistake }}</li>
</ul>
</div>
</div>
</div>
<!-- 底部交互提示 -->
<div class="interaction-hint">
💡 点击各层查看详细说明 | 实际调用流向从上到下依赖从下到上
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const activeLayer = ref('')
const setActiveLayer = (layer) => {
activeLayer.value = activeLayer.value === layer ? '' : layer
}
const layerInfo = computed(() => {
const infoMap = {
controller: {
title: 'Controller 层 - 请求的"门童"',
description: 'Controller 是系统的入口,负责接收 HTTP 请求、解析参数、进行基础校验,然后调用 Service 层处理业务。',
analogy: '就像餐厅的门童,负责迎接客人(接收请求)、检查预约(参数校验)、引导入座(路由到对应服务),但不负责做菜。',
mistakes: [
'在 Controller 里写业务逻辑(应该放在 Service)',
'直接操作数据库(应该调用 Repository',
'不做参数校验,导致脏数据流入系统'
]
},
service: {
title: 'Service 层 - 业务逻辑的"厨师"',
description: 'Service 是系统的核心,负责编排业务逻辑、管理事务、协调多个 Repository。这一层应该包含所有的业务规则和流程。',
analogy: '就像餐厅的厨师,负责按照菜谱(业务规则)做菜,需要协调各种食材(数据),把控菜品质量(业务正确性)。',
mistakes: [
'Service 之间互相调用,形成循环依赖',
'在 Service 里直接写 SQL(应该放在 Repository',
'一个 Service 方法过长,包含多个业务场景(应该拆分成多个方法)'
]
},
repository: {
title: 'Repository 层 - 数据的"仓管"',
description: 'Repository 负责与数据库交互,封装所有的 CRUD 操作。上层不需要关心具体的数据库类型和 SQL 语句。',
analogy: '就像餐厅的仓管员,负责从仓库(数据库)取食材、存放剩余食材,厨师(Service)只需要告诉他要什么,不需要知道仓库在哪。',
mistakes: [
'在 Repository 里写业务逻辑(应该只负责数据访问)',
'直接返回数据库实体给前端(应该转换为 DTO)',
'一个 Repository 操作多个表(应该拆分到不同 Repository'
]
},
domain: {
title: 'Domain 层 - 业务概念的"蓝图"',
description: 'Domain 定义了系统中的实体、值对象、业务规则。它是所有层的依赖基础,但不依赖任何其他层。',
analogy: '就像餐厅的菜单和菜品标准,定义了什么是"宫保鸡丁"、用什么食材、什么口味。所有厨师都要按照这个标准来做。',
mistakes: [
'Domain 对象里包含持久化注解(应该保持纯净)',
'在 Domain 里写业务逻辑(业务逻辑应该在 Service)',
'Domain 对象之间循环依赖'
]
}
}
return infoMap[activeLayer.value] || {}
})
</script>
<style scoped>
.layered-architecture-demo {
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
border-radius: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.architecture-container {
display: flex;
gap: 20px;
align-items: flex-start;
}
.client-layer {
display: flex;
flex-direction: column;
align-items: center;
}
.backend-layers {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.layer-box {
padding: 16px;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.layer-box:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.layer-box.active {
border-color: #409eff;
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.2);
}
/* 各层颜色主题 */
.controller { border-left: 4px solid #67c23a; }
.service { border-left: 4px solid #e6a23c; }
.repository { border-left: 4px solid #409eff; }
.domain { border-left: 4px solid #909399; }
.client { border-left: 4px solid #f56c6c; }
.database { border-left: 4px solid #8e44ad; }
.layer-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.layer-icon {
font-size: 20px;
}
.layer-name {
font-weight: 600;
font-size: 15px;
color: #303133;
}
.layer-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
background: #f0f2f5;
color: #606266;
}
.layer-content {
padding-left: 30px;
font-size: 13px;
color: #606266;
line-height: 1.6;
}
.duty, .tech {
margin: 4px 0;
}
.arrow-down {
text-align: center;
padding: 6px;
font-size: 12px;
color: #909399;
}
.info-panel {
width: 320px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
position: sticky;
top: 20px;
}
.info-panel h4 {
margin: 0 0 12px 0;
color: #303133;
font-size: 16px;
padding-bottom: 10px;
border-bottom: 2px solid #409eff;
}
.info-panel p {
margin: 0 0 16px 0;
color: #606266;
font-size: 13px;
line-height: 1.7;
}
.analogy, .common-mistakes {
margin: 12px 0;
padding: 12px;
border-radius: 6px;
font-size: 12px;
line-height: 1.6;
}
.analogy {
background: #f0f9ff;
border-left: 3px solid #409eff;
color: #1d4ed8;
}
.common-mistakes {
background: #fff2f0;
border-left: 3px solid #ff4d4f;
color: #cf1322;
}
.common-mistakes ul {
margin: 6px 0 0 0;
padding-left: 16px;
}
.common-mistakes li {
margin: 4px 0;
}
.interaction-hint {
text-align: center;
padding: 16px;
margin-top: 16px;
background: #f6ffed;
border: 1px solid #b7eb8f;
border-radius: 8px;
color: #389e0d;
font-size: 13px;
}
@media (max-width: 1024px) {
.architecture-container {
flex-direction: column;
}
.info-panel {
width: 100%;
position: static;
}
}
</style>