chore: save local history restorations from accidental git restore
This commit is contained in:
@@ -0,0 +1 @@
|
||||
**/*.md
|
||||
+388
-97
@@ -573,74 +573,224 @@ export default defineConfig({
|
||||
text: '一、计算机是怎么回事',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '从晶体管到 CPU', link: '/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu' },
|
||||
{ text: '操作系统(进程 / 内存 / 文件系统)', link: '/zh-cn/appendix/1-computer-fundamentals/operating-systems' },
|
||||
{ text: '数据的编码、存储与传输', link: '/zh-cn/appendix/1-computer-fundamentals/data-encoding-storage' },
|
||||
{ text: '网络:两台电脑如何对话', link: '/zh-cn/appendix/1-computer-fundamentals/computer-networks' },
|
||||
{ text: '数据结构', link: '/zh-cn/appendix/1-computer-fundamentals/data-structures' },
|
||||
{ text: '算法思维入门', link: '/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking' },
|
||||
{ text: '编程语言图谱', link: '/zh-cn/appendix/1-computer-fundamentals/programming-languages' },
|
||||
{ text: '类型系统与编译原理入门', link: '/zh-cn/appendix/1-computer-fundamentals/type-systems-compilers' }
|
||||
{
|
||||
text: '从晶体管到 CPU',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu'
|
||||
},
|
||||
{
|
||||
text: '操作系统(进程 / 内存 / 文件系统)',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/operating-systems'
|
||||
},
|
||||
{
|
||||
text: '数据的编码、存储与传输',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/data-encoding-storage'
|
||||
},
|
||||
{
|
||||
text: '网络:从输入网址到返回结果的过程',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/computer-networks'
|
||||
},
|
||||
{
|
||||
text: '数据结构',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/data-structures'
|
||||
},
|
||||
{
|
||||
text: '算法思维入门',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking'
|
||||
},
|
||||
{
|
||||
text: '编程语言图谱',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/programming-languages'
|
||||
},
|
||||
{
|
||||
text: '类型系统与编译原理入门',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/type-systems-compilers'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '二、开发环境与工具',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '集成开发环境 (IDE) 基础', link: '/zh-cn/appendix/2-development-tools/ide-basics' },
|
||||
{ text: '命令行与 Shell 脚本', link: '/zh-cn/appendix/2-development-tools/command-line-shell' },
|
||||
{ text: 'Git:代码的时光机', link: '/zh-cn/appendix/2-development-tools/git-version-control' },
|
||||
{ text: '环境变量与 PATH', link: '/zh-cn/appendix/2-development-tools/environment-path' },
|
||||
{ text: '端口与 localhost', link: '/zh-cn/appendix/2-development-tools/ports-localhost' },
|
||||
{ text: 'SSH 与密钥认证', link: '/zh-cn/appendix/2-development-tools/ssh-authentication' },
|
||||
{ text: '包管理器(npm / pip / cargo)', link: '/zh-cn/appendix/2-development-tools/package-managers' },
|
||||
{ text: '调试的艺术', link: '/zh-cn/appendix/2-development-tools/debugging-art/' },
|
||||
{ text: '正则表达式', link: '/zh-cn/appendix/2-development-tools/regex' }
|
||||
{
|
||||
text: '集成开发环境 (IDE) 基础',
|
||||
link: '/zh-cn/appendix/2-development-tools/ide-basics'
|
||||
},
|
||||
{
|
||||
text: '命令行与 Shell 脚本',
|
||||
link: '/zh-cn/appendix/2-development-tools/command-line-shell'
|
||||
},
|
||||
{
|
||||
text: 'Git:代码的时光机',
|
||||
link: '/zh-cn/appendix/2-development-tools/git-version-control'
|
||||
},
|
||||
{
|
||||
text: '环境变量与 PATH',
|
||||
link: '/zh-cn/appendix/2-development-tools/environment-path'
|
||||
},
|
||||
{
|
||||
text: '端口与 localhost',
|
||||
link: '/zh-cn/appendix/2-development-tools/ports-localhost'
|
||||
},
|
||||
{
|
||||
text: 'SSH 与密钥认证',
|
||||
link: '/zh-cn/appendix/2-development-tools/ssh-authentication'
|
||||
},
|
||||
{
|
||||
text: '包管理器(npm / pip / cargo)',
|
||||
link: '/zh-cn/appendix/2-development-tools/package-managers'
|
||||
},
|
||||
{
|
||||
text: '调试的艺术',
|
||||
link: '/zh-cn/appendix/2-development-tools/debugging-art/'
|
||||
},
|
||||
{
|
||||
text: '正则表达式',
|
||||
link: '/zh-cn/appendix/2-development-tools/regex'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '三、浏览器与前端',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'JavaScript 语言深入', link: '/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive' },
|
||||
{ text: 'TypeScript:给 JS 加上类型系统', link: '/zh-cn/appendix/3-browser-and-frontend/typescript' },
|
||||
{ text: '前端框架对比(React / Vue / Svelte / Angular)', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks' },
|
||||
{ text: '浏览器是一个操作系统', link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os' },
|
||||
{ text: '浏览器渲染管道', link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering' },
|
||||
{ text: 'HTML / CSS 布局体系', link: '/zh-cn/appendix/3-browser-and-frontend/html-css-layout' },
|
||||
{ text: 'JavaScript 运行时', link: '/zh-cn/appendix/3-browser-and-frontend/javascript-runtime' },
|
||||
{ text: '前端框架的本质', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-framework-nature' },
|
||||
{ text: '状态管理哲学', link: '/zh-cn/appendix/3-browser-and-frontend/state-management' },
|
||||
{ text: '路由与导航', link: '/zh-cn/appendix/3-browser-and-frontend/routing-navigation' },
|
||||
{ text: '图形与动画(Canvas / SVG / WebGL)', link: '/zh-cn/appendix/3-browser-and-frontend/graphics-animation' },
|
||||
{ text: '实时通信(WebSocket / SSE)', link: '/zh-cn/appendix/3-browser-and-frontend/realtime-communication' },
|
||||
{ text: '网页性能的度量与优化', link: '/zh-cn/appendix/3-browser-and-frontend/web-performance' },
|
||||
{ text: '前端工程化全貌', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-engineering' },
|
||||
{ text: '无障碍与国际化', link: '/zh-cn/appendix/3-browser-and-frontend/a11n-i18n' }
|
||||
{
|
||||
text: 'JavaScript 语言深入',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive'
|
||||
},
|
||||
{
|
||||
text: 'TypeScript:给 JS 加上类型系统',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/typescript'
|
||||
},
|
||||
{
|
||||
text: '前端框架对比(React / Vue / Svelte / Angular)',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks'
|
||||
},
|
||||
{
|
||||
text: '浏览器是一个操作系统',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os'
|
||||
},
|
||||
{
|
||||
text: '浏览器渲染管道',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering'
|
||||
},
|
||||
{
|
||||
text: 'HTML / CSS 布局体系',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/html-css-layout'
|
||||
},
|
||||
{
|
||||
text: 'JavaScript 运行时',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/javascript-runtime'
|
||||
},
|
||||
{
|
||||
text: '前端框架的本质',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-framework-nature'
|
||||
},
|
||||
{
|
||||
text: '状态管理哲学',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/state-management'
|
||||
},
|
||||
{
|
||||
text: '路由与导航',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/routing-navigation'
|
||||
},
|
||||
{
|
||||
text: '图形与动画(Canvas / SVG / WebGL)',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/graphics-animation'
|
||||
},
|
||||
{
|
||||
text: '实时通信(WebSocket / SSE)',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/realtime-communication'
|
||||
},
|
||||
{
|
||||
text: '网页性能的度量与优化',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/web-performance'
|
||||
},
|
||||
{
|
||||
text: '前端工程化全貌',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-engineering'
|
||||
},
|
||||
{
|
||||
text: '无障碍与国际化',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/a11n-i18n'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '四、服务器与后端',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '后端语言对比(Node.js / Go / Java / Rust)', link: '/zh-cn/appendix/4-server-and-backend/backend-languages' },
|
||||
{ text: '客户端语言对比(Swift / Kotlin / Dart)', link: '/zh-cn/appendix/4-server-and-backend/client-languages' },
|
||||
{ text: '跨平台方案对比(React Native / Flutter / Electron / Tauri)', link: '/zh-cn/appendix/4-server-and-backend/cross-platform' },
|
||||
{ text: 'HTTP 协议', link: '/zh-cn/appendix/4-server-and-backend/http-protocol' },
|
||||
{ text: '一个请求的完整旅程', link: '/zh-cn/appendix/4-server-and-backend/request-journey' },
|
||||
{ text: 'Web 框架的本质', link: '/zh-cn/appendix/4-server-and-backend/web-frameworks' },
|
||||
{ text: 'API 入门', link: '/zh-cn/appendix/4-server-and-backend/api-intro' },
|
||||
{ text: 'API 设计哲学(REST / GraphQL / gRPC)', link: '/zh-cn/appendix/4-server-and-backend/api-design' },
|
||||
{ text: '序列化与数据格式', link: '/zh-cn/appendix/4-server-and-backend/serialization' },
|
||||
{ text: '认证与授权体系', link: '/zh-cn/appendix/4-server-and-backend/auth-authorization' },
|
||||
{ text: '并发、异步与多线程', link: '/zh-cn/appendix/4-server-and-backend/concurrency-async' },
|
||||
{ text: '缓存的层次与策略', link: '/zh-cn/appendix/4-server-and-backend/caching' },
|
||||
{ text: '消息队列与事件驱动', link: '/zh-cn/appendix/4-server-and-backend/message-queues' },
|
||||
{ text: '异步任务队列与生产消费模型', link: '/zh-cn/appendix/4-server-and-backend/async-task-queues' },
|
||||
{ text: '限流与背压控制', link: '/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure' },
|
||||
{ text: '搜索引擎原理', link: '/zh-cn/appendix/4-server-and-backend/search-engines' },
|
||||
{ text: '文件存储与对象存储', link: '/zh-cn/appendix/4-server-and-backend/file-storage' },
|
||||
{ text: '后端分层架构', link: '/zh-cn/appendix/4-server-and-backend/backend-layered-architecture' }
|
||||
{
|
||||
text: '后端语言对比(Node.js / Go / Java / Rust)',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/backend-languages'
|
||||
},
|
||||
{
|
||||
text: '客户端语言对比(Swift / Kotlin / Dart)',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/client-languages'
|
||||
},
|
||||
{
|
||||
text: '跨平台方案对比(React Native / Flutter / Electron / Tauri)',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/cross-platform'
|
||||
},
|
||||
{
|
||||
text: 'HTTP 协议',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/http-protocol'
|
||||
},
|
||||
{
|
||||
text: '一个请求的完整旅程',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/request-journey'
|
||||
},
|
||||
{
|
||||
text: 'Web 框架的本质',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/web-frameworks'
|
||||
},
|
||||
{
|
||||
text: 'API 入门',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/api-intro'
|
||||
},
|
||||
{
|
||||
text: 'API 设计哲学(REST / GraphQL / gRPC)',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/api-design'
|
||||
},
|
||||
{
|
||||
text: '序列化与数据格式',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/serialization'
|
||||
},
|
||||
{
|
||||
text: '认证与授权体系',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/auth-authorization'
|
||||
},
|
||||
{
|
||||
text: '并发、异步与多线程',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/concurrency-async'
|
||||
},
|
||||
{
|
||||
text: '缓存的层次与策略',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/caching'
|
||||
},
|
||||
{
|
||||
text: '消息队列与事件驱动',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/message-queues'
|
||||
},
|
||||
{
|
||||
text: '异步任务队列与生产消费模型',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/async-task-queues'
|
||||
},
|
||||
{
|
||||
text: '限流与背压控制',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure'
|
||||
},
|
||||
{
|
||||
text: '搜索引擎原理',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/search-engines'
|
||||
},
|
||||
{
|
||||
text: '文件存储与对象存储',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/file-storage'
|
||||
},
|
||||
{
|
||||
text: '后端分层架构',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/backend-layered-architecture'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -648,77 +798,218 @@ export default defineConfig({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'SQL', link: '/zh-cn/appendix/5-data/sql' },
|
||||
{ text: '数据库原理(索引 / 事务 / 查询优化)', link: '/zh-cn/appendix/5-data/database-fundamentals' },
|
||||
{ text: '数据模型全景(文档 / 图 / 时序 / 向量)', link: '/zh-cn/appendix/5-data/data-models' },
|
||||
{ text: '数据埋点与用户行为采集', link: '/zh-cn/appendix/5-data/data-tracking' },
|
||||
{ text: '数据分析基础(统计 / 指标 / 漏斗)', link: '/zh-cn/appendix/5-data/data-analysis' },
|
||||
{ text: 'A/B 测试与实验驱动', link: '/zh-cn/appendix/5-data/ab-testing' },
|
||||
{ text: '数据可视化与仪表盘', link: '/zh-cn/appendix/5-data/data-visualization' },
|
||||
{ text: '数据治理与数据质量', link: '/zh-cn/appendix/5-data/data-governance' }
|
||||
{
|
||||
text: '数据库原理(索引 / 事务 / 查询优化)',
|
||||
link: '/zh-cn/appendix/5-data/database-fundamentals'
|
||||
},
|
||||
{
|
||||
text: '数据模型全景(文档 / 图 / 时序 / 向量)',
|
||||
link: '/zh-cn/appendix/5-data/data-models'
|
||||
},
|
||||
{
|
||||
text: '数据埋点与用户行为采集',
|
||||
link: '/zh-cn/appendix/5-data/data-tracking'
|
||||
},
|
||||
{
|
||||
text: '数据分析基础(统计 / 指标 / 漏斗)',
|
||||
link: '/zh-cn/appendix/5-data/data-analysis'
|
||||
},
|
||||
{
|
||||
text: 'A/B 测试与实验驱动',
|
||||
link: '/zh-cn/appendix/5-data/ab-testing'
|
||||
},
|
||||
{
|
||||
text: '数据可视化与仪表盘',
|
||||
link: '/zh-cn/appendix/5-data/data-visualization'
|
||||
},
|
||||
{
|
||||
text: '数据治理与数据质量',
|
||||
link: '/zh-cn/appendix/5-data/data-governance'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '六、架构与系统设计',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '从单体到微服务的演进', link: '/zh-cn/appendix/6-architecture-and-system-design/monolith-to-microservices' },
|
||||
{ text: '分布式系统的挑战', link: '/zh-cn/appendix/6-architecture-and-system-design/distributed-systems' },
|
||||
{ text: '高可用与容灾', link: '/zh-cn/appendix/6-architecture-and-system-design/high-availability' },
|
||||
{ text: '系统设计方法论', link: '/zh-cn/appendix/6-architecture-and-system-design/system-design-methodology' }
|
||||
{
|
||||
text: '从单体到微服务的演进',
|
||||
link: '/zh-cn/appendix/6-architecture-and-system-design/monolith-to-microservices'
|
||||
},
|
||||
{
|
||||
text: '分布式系统的挑战',
|
||||
link: '/zh-cn/appendix/6-architecture-and-system-design/distributed-systems'
|
||||
},
|
||||
{
|
||||
text: '高可用与容灾',
|
||||
link: '/zh-cn/appendix/6-architecture-and-system-design/high-availability'
|
||||
},
|
||||
{
|
||||
text: '系统设计方法论',
|
||||
link: '/zh-cn/appendix/6-architecture-and-system-design/system-design-methodology'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '七、基础设施与运维',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'Linux 基础', link: '/zh-cn/appendix/7-infrastructure-and-operations/linux-basics' },
|
||||
{ text: 'Docker 容器化', link: '/zh-cn/appendix/7-infrastructure-and-operations/docker-containers' },
|
||||
{ text: 'Kubernetes 编排', link: '/zh-cn/appendix/7-infrastructure-and-operations/kubernetes' },
|
||||
{ text: 'CI / CD 自动化', link: '/zh-cn/appendix/7-infrastructure-and-operations/ci-cd' },
|
||||
{ text: '域名、DNS 与 HTTPS', link: '/zh-cn/appendix/7-infrastructure-and-operations/dns-https' },
|
||||
{ text: '负载均衡与网关', link: '/zh-cn/appendix/7-infrastructure-and-operations/load-balancing-gateway' },
|
||||
{ text: '网关与反向代理', link: '/zh-cn/appendix/7-infrastructure-and-operations/gateway-proxy' },
|
||||
{ text: '云平台实战', link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-platforms' },
|
||||
{ text: 'IAM 权限管理', link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-iam' },
|
||||
{ text: '对象存储与 CDN', link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-storage-cdn' },
|
||||
{ text: '基础设施即代码', link: '/zh-cn/appendix/7-infrastructure-and-operations/infrastructure-as-code' },
|
||||
{ text: '监控、日志与告警', link: '/zh-cn/appendix/7-infrastructure-and-operations/monitoring-logging' },
|
||||
{ text: '故障排查与应急响应', link: '/zh-cn/appendix/7-infrastructure-and-operations/incident-response' }
|
||||
{
|
||||
text: 'Linux 基础',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/linux-basics'
|
||||
},
|
||||
{
|
||||
text: 'Docker 容器化',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/docker-containers'
|
||||
},
|
||||
{
|
||||
text: 'Kubernetes 编排',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/kubernetes'
|
||||
},
|
||||
{
|
||||
text: 'CI / CD 自动化',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/ci-cd'
|
||||
},
|
||||
{
|
||||
text: '域名、DNS 与 HTTPS',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/dns-https'
|
||||
},
|
||||
{
|
||||
text: '负载均衡与网关',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/load-balancing-gateway'
|
||||
},
|
||||
{
|
||||
text: '网关与反向代理',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/gateway-proxy'
|
||||
},
|
||||
{
|
||||
text: '云平台实战',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-platforms'
|
||||
},
|
||||
{
|
||||
text: 'IAM 权限管理',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-iam'
|
||||
},
|
||||
{
|
||||
text: '对象存储与 CDN',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-storage-cdn'
|
||||
},
|
||||
{
|
||||
text: '基础设施即代码',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/infrastructure-as-code'
|
||||
},
|
||||
{
|
||||
text: '监控、日志与告警',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/monitoring-logging'
|
||||
},
|
||||
{
|
||||
text: '故障排查与应急响应',
|
||||
link: '/zh-cn/appendix/7-infrastructure-and-operations/incident-response'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '八、人工智能',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: 'AI 简史与核心概念', link: '/zh-cn/appendix/8-artificial-intelligence/ai-history' },
|
||||
{ text: '神经网络与深度学习', link: '/zh-cn/appendix/8-artificial-intelligence/neural-networks' },
|
||||
{ text: 'Transformer 与注意力机制', link: '/zh-cn/appendix/8-artificial-intelligence/transformer-attention' },
|
||||
{ text: '大语言模型的工作原理', link: '/zh-cn/appendix/8-artificial-intelligence/llm-principles' },
|
||||
{ text: '提示词工程', link: '/zh-cn/appendix/8-artificial-intelligence/prompt-engineering' },
|
||||
{ text: '上下文工程', link: '/zh-cn/appendix/8-artificial-intelligence/context-engineering' },
|
||||
{ text: '多模态模型(视觉 / 音频 / 视频)', link: '/zh-cn/appendix/8-artificial-intelligence/multimodal-models' },
|
||||
{ text: '图像生成原理', link: '/zh-cn/appendix/8-artificial-intelligence/image-generation' },
|
||||
{ text: '语音合成与识别', link: '/zh-cn/appendix/8-artificial-intelligence/speech-synthesis-recognition' },
|
||||
{ text: 'Embedding 与向量检索', link: '/zh-cn/appendix/8-artificial-intelligence/embedding-vector-retrieval' },
|
||||
{ text: 'RAG 架构', link: '/zh-cn/appendix/8-artificial-intelligence/rag' },
|
||||
{ text: 'AI Agent 与工具调用', link: '/zh-cn/appendix/8-artificial-intelligence/ai-agents' },
|
||||
{ text: 'AI 协议(MCP & A2A)', link: '/zh-cn/appendix/8-artificial-intelligence/ai-protocols' },
|
||||
{ text: '模型微调与部署', link: '/zh-cn/appendix/8-artificial-intelligence/model-finetuning-deployment' },
|
||||
{ text: 'AI 原生应用设计', link: '/zh-cn/appendix/8-artificial-intelligence/ai-native-app-design' },
|
||||
{ text: 'AI 能力词典', link: '/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary' }
|
||||
{
|
||||
text: 'AI 简史与核心概念',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-history'
|
||||
},
|
||||
{
|
||||
text: '神经网络与深度学习',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/neural-networks'
|
||||
},
|
||||
{
|
||||
text: 'Transformer 与注意力机制',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/transformer-attention'
|
||||
},
|
||||
{
|
||||
text: '大语言模型的工作原理',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/llm-principles'
|
||||
},
|
||||
{
|
||||
text: '提示词工程',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/prompt-engineering'
|
||||
},
|
||||
{
|
||||
text: '上下文工程',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/context-engineering'
|
||||
},
|
||||
{
|
||||
text: '多模态模型(视觉 / 音频 / 视频)',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/multimodal-models'
|
||||
},
|
||||
{
|
||||
text: '图像生成原理',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/image-generation'
|
||||
},
|
||||
{
|
||||
text: '语音合成与识别',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/speech-synthesis-recognition'
|
||||
},
|
||||
{
|
||||
text: 'Embedding 与向量检索',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/embedding-vector-retrieval'
|
||||
},
|
||||
{
|
||||
text: 'RAG 架构',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/rag'
|
||||
},
|
||||
{
|
||||
text: 'AI Agent 与工具调用',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-agents'
|
||||
},
|
||||
{
|
||||
text: 'AI 协议(MCP & A2A)',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-protocols'
|
||||
},
|
||||
{
|
||||
text: '模型微调与部署',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/model-finetuning-deployment'
|
||||
},
|
||||
{
|
||||
text: 'AI 原生应用设计',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-native-app-design'
|
||||
},
|
||||
{
|
||||
text: 'AI 能力词典',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '九、工程素养',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '代码质量与重构', link: '/zh-cn/appendix/9-engineering-excellence/code-quality-refactoring' },
|
||||
{ text: '测试策略', link: '/zh-cn/appendix/9-engineering-excellence/testing-strategies' },
|
||||
{ text: '设计模式', link: '/zh-cn/appendix/9-engineering-excellence/design-patterns' },
|
||||
{ text: '安全思维与攻防基础', link: '/zh-cn/appendix/9-engineering-excellence/security-thinking' },
|
||||
{ text: '技术文档写作', link: '/zh-cn/appendix/9-engineering-excellence/technical-writing' },
|
||||
{ text: '开源协作', link: '/zh-cn/appendix/9-engineering-excellence/open-source-collaboration' },
|
||||
{ text: '技术选型方法论', link: '/zh-cn/appendix/9-engineering-excellence/technology-selection' }
|
||||
{
|
||||
text: '代码质量与重构',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/code-quality-refactoring'
|
||||
},
|
||||
{
|
||||
text: '测试策略',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/testing-strategies'
|
||||
},
|
||||
{
|
||||
text: '设计模式',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/design-patterns'
|
||||
},
|
||||
{
|
||||
text: '安全思维与攻防基础',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/security-thinking'
|
||||
},
|
||||
{
|
||||
text: '技术文档写作',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/technical-writing'
|
||||
},
|
||||
{
|
||||
text: '开源协作',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/open-source-collaboration'
|
||||
},
|
||||
{
|
||||
text: '技术选型方法论',
|
||||
link: '/zh-cn/appendix/9-engineering-excellence/technology-selection'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="header">
|
||||
<span class="icon">🎨</span>
|
||||
<span class="title">四种 API 风格对比</span>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="style in styles"
|
||||
:key="style.id"
|
||||
:class="['tab', { active: active === style.id }]"
|
||||
@click="active = style.id"
|
||||
>
|
||||
{{ style.icon }} {{ style.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="style-header">
|
||||
<h4>{{ currentStyle.name }}</h4>
|
||||
<span class="badge">{{ currentStyle.badge }}</span>
|
||||
</div>
|
||||
|
||||
<p class="desc">{{ currentStyle.desc }}</p>
|
||||
|
||||
<div class="example-section">
|
||||
<div class="example-label">示例:获取用户信息</div>
|
||||
<pre class="code-block"><code>{{ currentStyle.example }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
<div class="features-title">核心特点</div>
|
||||
<div class="features-grid">
|
||||
<div
|
||||
v-for="(f, i) in currentStyle.features"
|
||||
:key="i"
|
||||
class="feature-item"
|
||||
>
|
||||
<span class="check">✓</span>
|
||||
<span>{{ f }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">适用场景</span>
|
||||
<span class="meta-value">{{ currentStyle.scenarios }}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">官方地址</span>
|
||||
<a :href="currentStyle.official" target="_blank" class="meta-link">{{
|
||||
currentStyle.official
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="compare-section">
|
||||
<div class="compare-title">📊 风格速览对比</div>
|
||||
<div class="compare-table">
|
||||
<div class="compare-row head">
|
||||
<div class="cell">特性</div>
|
||||
<div class="cell">RPC</div>
|
||||
<div class="cell highlight">REST</div>
|
||||
<div class="cell">GraphQL</div>
|
||||
<div class="cell">gRPC</div>
|
||||
</div>
|
||||
<div class="compare-row">
|
||||
<div class="cell">核心理念</div>
|
||||
<div class="cell">面向过程</div>
|
||||
<div class="cell highlight">面向资源</div>
|
||||
<div class="cell">面向数据</div>
|
||||
<div class="cell">面向方法</div>
|
||||
</div>
|
||||
<div class="compare-row">
|
||||
<div class="cell">URL 风格</div>
|
||||
<div class="cell">动词为主</div>
|
||||
<div class="cell highlight">名词为主</div>
|
||||
<div class="cell">单一端点</div>
|
||||
<div class="cell">不依赖URL</div>
|
||||
</div>
|
||||
<div class="compare-row">
|
||||
<div class="cell">学习曲线</div>
|
||||
<div class="cell low">低</div>
|
||||
<div class="cell">中</div>
|
||||
<div class="cell">中</div>
|
||||
<div class="cell high">高</div>
|
||||
</div>
|
||||
<div class="compare-row">
|
||||
<div class="cell">性能</div>
|
||||
<div class="cell">一般</div>
|
||||
<div class="cell">一般</div>
|
||||
<div class="cell">较好</div>
|
||||
<div class="cell best">优秀</div>
|
||||
</div>
|
||||
<div class="compare-row">
|
||||
<div class="cell">使用占比</div>
|
||||
<div class="cell">~30%</div>
|
||||
<div class="cell highlight">~50%</div>
|
||||
<div class="cell">~15%</div>
|
||||
<div class="cell">~5%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const active = ref('rest')
|
||||
|
||||
const styles = [
|
||||
{
|
||||
id: 'rpc',
|
||||
icon: '📞',
|
||||
name: 'RPC',
|
||||
badge: '最传统',
|
||||
desc: 'Remote Procedure Call,远程过程调用。像调用本地方法一样调用远程服务,面向过程,简单直接。超过 50% 的内部 API 采用这种风格。',
|
||||
example: `GET /getUserInfo?id=123
|
||||
POST /createUser
|
||||
POST /deleteOrder
|
||||
GET /queryUserList`,
|
||||
features: [
|
||||
'URL 命名往往是动词',
|
||||
'HTTP 方法基本只用 GET/POST',
|
||||
'设计简单,几乎无约束',
|
||||
'需要详细文档说明'
|
||||
],
|
||||
scenarios: '内部 API、性能敏感场景、难以抽象为资源的业务',
|
||||
official: '无官方规范(概念性风格)'
|
||||
},
|
||||
{
|
||||
id: 'rest',
|
||||
icon: '🌐',
|
||||
name: 'REST',
|
||||
badge: '最常用',
|
||||
desc: 'Representational State Transfer,表述性状态转移。由 Roy Fielding 于 2000 年在其博士论文中提出。面向资源,用 URL 标识资源,用 HTTP 方法操作资源。',
|
||||
example: `GET /users # 获取用户列表
|
||||
GET /users/123 # 获取单个用户
|
||||
POST /users # 创建用户
|
||||
PUT /users/123 # 全量更新
|
||||
PATCH /users/123 # 部分更新
|
||||
DELETE /users/123 # 删除用户`,
|
||||
features: [
|
||||
'URL 是名词,不是动词',
|
||||
'使用 HTTP 方法表达操作',
|
||||
'无状态,请求包含所有信息',
|
||||
'可缓存,支持分层系统'
|
||||
],
|
||||
scenarios: '公开 API、CRUD 操作、资源边界清晰的业务',
|
||||
official: 'https://restfulapi.net/'
|
||||
},
|
||||
{
|
||||
id: 'graphql',
|
||||
icon: '📊',
|
||||
name: 'GraphQL',
|
||||
badge: '最灵活',
|
||||
desc: '由 Facebook 于 2015 年开源。一种查询语言,客户端可以精确指定需要的数据字段,避免过度获取或获取不足。',
|
||||
example: `query {
|
||||
user(id: "123") {
|
||||
name
|
||||
email
|
||||
orders {
|
||||
id
|
||||
total
|
||||
}
|
||||
}
|
||||
}`,
|
||||
features: [
|
||||
'单一端点(/graphql)',
|
||||
'客户端决定返回字段',
|
||||
'Schema 即文档',
|
||||
'一次请求获取多资源'
|
||||
],
|
||||
scenarios: '客户端需求多变、数据关系复杂、移动端 App',
|
||||
official: 'https://graphql.org/'
|
||||
},
|
||||
{
|
||||
id: 'grpc',
|
||||
icon: '⚡',
|
||||
name: 'gRPC',
|
||||
badge: '最高效',
|
||||
desc: '由 Google 于 2016 年开源。高性能 RPC 框架,使用 Protocol Buffers 序列化,基于 HTTP/2,支持双向流通信。',
|
||||
example: `service UserService {
|
||||
rpc GetUser(GetUserRequest) returns (User);
|
||||
rpc CreateUser(CreateUserRequest) returns (User);
|
||||
}
|
||||
|
||||
message User {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
}`,
|
||||
features: [
|
||||
'二进制传输,性能极高',
|
||||
'强类型,代码自动生成',
|
||||
'基于 HTTP/2,双向流',
|
||||
'浏览器支持差'
|
||||
],
|
||||
scenarios: '微服务内部通信、高性能场景、强类型需求',
|
||||
official: 'https://grpc.io/'
|
||||
}
|
||||
]
|
||||
|
||||
const currentStyle = computed(() => {
|
||||
return styles.find((s) => s.id === active.value) || styles[1]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 14px 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 12px 16px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.style-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.style-header h4 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.example-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.example-label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
font-family: 'Menlo', 'Monaco', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.features {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.features-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.check {
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.meta {
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: var(--vp-c-text-3);
|
||||
min-width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.meta-link {
|
||||
color: var(--vp-c-brand);
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.meta-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.compare-section {
|
||||
background: var(--vp-c-bg);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.compare-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.compare-table {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.compare-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.compare-row:nth-child(odd) {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.compare-row:nth-child(even) {
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.compare-row.head {
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.cell {
|
||||
padding: 10px 8px;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-align: center;
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.head .cell {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.cell:first-child {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-1);
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.cell.highlight {
|
||||
background: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cell.low {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.cell.high {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.cell.best {
|
||||
color: #22c55e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.compare-row {
|
||||
grid-template-columns: 70px repeat(4, 1fr);
|
||||
}
|
||||
.cell {
|
||||
padding: 8px 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,533 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="header">
|
||||
<span class="icon">📦</span>
|
||||
<span class="title">data 字段设计规范</span>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:class="['tab', { active: active === tab.id }]"
|
||||
@click="active = tab.id"
|
||||
>
|
||||
{{ tab.icon }} {{ tab.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div v-if="active === 'structure'" class="section">
|
||||
<h4>单对象 vs 列表</h4>
|
||||
<div class="compare-row">
|
||||
<div class="compare-col">
|
||||
<div class="compare-title">单对象</div>
|
||||
<pre class="code-sm">{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": 123,
|
||||
"name": "张三"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
<div class="compare-col">
|
||||
<div class="compare-title">列表</div>
|
||||
<pre class="code-sm">{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"total": 100
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note">列表数据包裹在 items 数组中,分页信息放在 pagination 对象</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'naming'" class="section">
|
||||
<h4>字段命名规范</h4>
|
||||
<div class="rule-list">
|
||||
<div
|
||||
v-for="rule in namingRules"
|
||||
:key="rule.name"
|
||||
class="rule-item"
|
||||
>
|
||||
<div class="rule-header">
|
||||
<span class="rule-icon">{{ rule.icon }}</span>
|
||||
<span class="rule-name">{{ rule.name }}</span>
|
||||
</div>
|
||||
<div class="rule-examples">
|
||||
<code class="good">{{ rule.good }}</code>
|
||||
<span
|
||||
v-if="rule.bad"
|
||||
class="vs"
|
||||
>vs</span>
|
||||
<code
|
||||
v-if="rule.bad"
|
||||
class="bad"
|
||||
>{{ rule.bad }}</code>
|
||||
</div>
|
||||
<div class="rule-desc">{{ rule.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'datetime'" class="section">
|
||||
<h4>时间格式设计</h4>
|
||||
<div class="time-example">
|
||||
<pre class="code-block">{
|
||||
"created_at": "2024-01-15T09:30:00.000Z",
|
||||
"updated_at": "2024-01-15T10:00:00.000Z",
|
||||
"expired_at": "2025-01-15T00:00:00.000Z"
|
||||
}</pre>
|
||||
</div>
|
||||
<div class="time-rules">
|
||||
<div class="time-rule">
|
||||
<span class="rule-label">格式</span>
|
||||
<span class="rule-value">ISO 8601</span>
|
||||
</div>
|
||||
<div class="time-rule">
|
||||
<span class="rule-label">时区</span>
|
||||
<span class="rule-value">UTC(Z 后缀)或明确偏移量</span>
|
||||
</div>
|
||||
<div class="time-rule">
|
||||
<span class="rule-label">精度</span>
|
||||
<span class="rule-value">毫秒 .000Z</span>
|
||||
</div>
|
||||
<div class="time-rule">
|
||||
<span class="rule-label">命名</span>
|
||||
<span class="rule-value">xxx_at 表示时间点,xxx_duration 表示时长</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'null'" class="section">
|
||||
<h4>空值处理</h4>
|
||||
<div class="compare-row">
|
||||
<div class="compare-col good-col">
|
||||
<div class="compare-title">✅ 推荐</div>
|
||||
<pre class="code-sm">{
|
||||
"name": "张三",
|
||||
"nickname": null,
|
||||
"avatar": null
|
||||
}</pre>
|
||||
<div class="compare-desc">字段存在但无值时返回 null</div>
|
||||
</div>
|
||||
<div class="compare-col bad-col">
|
||||
<div class="compare-title">❌ 不推荐</div>
|
||||
<pre class="code-sm">{
|
||||
"name": "张三"
|
||||
}</pre>
|
||||
<div class="compare-desc">省略字段,前端需判断是否存在</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="null-tips">
|
||||
<div class="tip-item">空数组返回 <code>[]</code></div>
|
||||
<div class="tip-item">空对象返回 <code>{}</code></div>
|
||||
<div class="tip-item">前端可统一处理,无需判断字段是否存在</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'relation'" class="section">
|
||||
<h4>关联数据设计</h4>
|
||||
<div class="relation-tabs">
|
||||
<button
|
||||
v-for="r in relations"
|
||||
:key="r.id"
|
||||
:class="['r-tab', { active: rId === r.id }]"
|
||||
@click="rId = r.id"
|
||||
>
|
||||
{{ r.name }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="relation-content">
|
||||
<div class="relation-desc">{{ currentRelation.desc }}</div>
|
||||
<pre class="code-block"><code>{{ currentRelation.code }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
<span class="tips-icon">💡</span>
|
||||
<span class="tips-text">参考 ISO 8601 时间标准,字段命名保持 snake_case 风格</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const active = ref('structure')
|
||||
const rId = ref('embed')
|
||||
|
||||
const tabs = [
|
||||
{ id: 'structure', icon: '📐', name: '结构设计' },
|
||||
{ id: 'naming', icon: '📝', name: '命名规范' },
|
||||
{ id: 'datetime', icon: '🕐', name: '时间格式' },
|
||||
{ id: 'null', icon: '∅', name: '空值处理' },
|
||||
{ id: 'relation', icon: '🔗', name: '关联数据' }
|
||||
]
|
||||
|
||||
const namingRules = [
|
||||
{ icon: '🔡', name: '使用 snake_case', good: 'created_at', bad: 'createdAt', desc: 'JSON 字段名统一用下划线' },
|
||||
{ icon: '📖', name: '避免缩写', good: 'user_id', bad: 'uid', desc: '保持可读性' },
|
||||
{ icon: '✅', name: '布尔值加前缀', good: 'is_active, has_permission', bad: 'active, permission', desc: '一眼识别布尔类型' },
|
||||
{ icon: '📅', name: '时间带后缀', good: 'created_at, expired_at', bad: 'created, expired', desc: '明确是时间字段' },
|
||||
{ icon: '🔢', name: '数量带后缀', good: 'total_count, page_size', bad: 'total, size', desc: '明确是数值类型' }
|
||||
]
|
||||
|
||||
const relations = [
|
||||
{
|
||||
id: 'embed',
|
||||
name: '内嵌',
|
||||
desc: '适合数据量小、频繁访问的关联数据',
|
||||
code: `{
|
||||
"id": 123,
|
||||
"name": "张三",
|
||||
"department": {
|
||||
"id": 1,
|
||||
"name": "技术部"
|
||||
}
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'foreign',
|
||||
name: '外键',
|
||||
desc: '适合数据量大、按需加载的关联数据',
|
||||
code: `{
|
||||
"id": 123,
|
||||
"name": "张三",
|
||||
"department_id": 1
|
||||
}`
|
||||
},
|
||||
{
|
||||
id: 'expand',
|
||||
name: 'expand 参数',
|
||||
desc: 'Stripe 风格,客户端按需展开',
|
||||
code: `// GET /users/123?expand=department
|
||||
{
|
||||
"id": 123,
|
||||
"name": "张三",
|
||||
"department": {
|
||||
"id": 1,
|
||||
"name": "技术部"
|
||||
}
|
||||
}`
|
||||
}
|
||||
]
|
||||
|
||||
const currentRelation = computed(() => {
|
||||
return relations.find(r => r.id === rId.value) || relations[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 14px 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.compare-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.compare-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.compare-col {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.compare-col.good-col {
|
||||
border-color: color-mix(in srgb, #22c55e 30%, transparent);
|
||||
background: color-mix(in srgb, #22c55e 5%, var(--vp-c-bg));
|
||||
}
|
||||
|
||||
.compare-col.bad-col {
|
||||
border-color: color-mix(in srgb, #ef4444 30%, transparent);
|
||||
background: color-mix(in srgb, #ef4444 5%, var(--vp-c-bg));
|
||||
}
|
||||
|
||||
.compare-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.compare-desc {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.code-sm {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 12px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rule-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.rule-item {
|
||||
padding: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rule-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rule-examples {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.rule-examples code {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rule-examples .good {
|
||||
background: color-mix(in srgb, #22c55e 15%, transparent);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.rule-examples .bad {
|
||||
background: color-mix(in srgb, #ef4444 15%, transparent);
|
||||
color: #dc2626;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.rule-examples .vs {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.rule-desc {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.time-example {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.time-rules {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.time-rule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rule-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.rule-value {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.null-tips {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 6px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tip-item code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.relation-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.r-tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.r-tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.r-tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.relation-desc {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: var(--vp-c-bg);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.tips-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,575 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="header">
|
||||
<span class="icon">⚠️</span>
|
||||
<span class="title">错误响应设计进阶</span>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:class="['tab', { active: active === tab.id }]"
|
||||
@click="active = tab.id"
|
||||
>
|
||||
{{ tab.icon }} {{ tab.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div v-if="active === 'validate'" class="section">
|
||||
<h4>参数校验错误</h4>
|
||||
<pre class="code-block">{
|
||||
"code": 10001,
|
||||
"message": "参数校验失败",
|
||||
"data": {
|
||||
"errors": [
|
||||
{
|
||||
"field": "email",
|
||||
"message": "邮箱格式不正确",
|
||||
"value": "invalid-email"
|
||||
},
|
||||
{
|
||||
"field": "password",
|
||||
"message": "密码长度至少 8 位",
|
||||
"value": "123"
|
||||
}
|
||||
]
|
||||
}
|
||||
}</pre>
|
||||
<div class="field-tips">
|
||||
<div class="tip-row">
|
||||
<code>field</code>
|
||||
<span>出错字段名,前端可定位表单</span>
|
||||
</div>
|
||||
<div class="tip-row">
|
||||
<code>message</code>
|
||||
<span>用户友好的错误描述</span>
|
||||
</div>
|
||||
<div class="tip-row">
|
||||
<code>value</code>
|
||||
<span>客户端提交的值(可选)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'business'" class="section">
|
||||
<h4>业务错误</h4>
|
||||
<pre class="code-block">{
|
||||
"code": 20001,
|
||||
"message": "余额不足",
|
||||
"data": {
|
||||
"current_balance": 50.00,
|
||||
"required_amount": 99.00,
|
||||
"shortfall": 49.00,
|
||||
"suggestion": "请充值后重试"
|
||||
}
|
||||
}</pre>
|
||||
<div class="business-tips">
|
||||
<div class="b-tip">✓ 返回当前状态数据,便于前端展示</div>
|
||||
<div class="b-tip">✓ 提供 suggestion 给出解决建议</div>
|
||||
<div class="b-tip">✓ 数据结构化,前端可灵活展示</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'layers'" class="section">
|
||||
<h4>错误码分层设计</h4>
|
||||
<div class="layer-list">
|
||||
<div
|
||||
v-for="layer in layers"
|
||||
:key="layer.range"
|
||||
class="layer-item"
|
||||
>
|
||||
<div class="layer-range">{{ layer.range }}</div>
|
||||
<div class="layer-info">
|
||||
<div class="layer-name">{{ layer.name }}</div>
|
||||
<div class="layer-example">示例:{{ layer.example }}</div>
|
||||
</div>
|
||||
<div class="layer-desc">{{ layer.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-note">错误码从外到内:系统 → 服务 → 业务 → 认证 → 参数</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'http'" class="section">
|
||||
<h4>HTTP 状态码 vs 业务状态码</h4>
|
||||
<div class="http-compare">
|
||||
<div class="http-col">
|
||||
<div class="http-title">HTTP 状态码</div>
|
||||
<div class="http-desc">传输层状态</div>
|
||||
<div class="http-codes">
|
||||
<div class="http-code">
|
||||
<span class="code-num">2xx</span>
|
||||
<span>请求成功</span>
|
||||
</div>
|
||||
<div class="http-code">
|
||||
<span class="code-num">4xx</span>
|
||||
<span>客户端错误</span>
|
||||
</div>
|
||||
<div class="http-code">
|
||||
<span class="code-num">5xx</span>
|
||||
<span>服务端错误</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="http-arrow">→</div>
|
||||
<div class="http-col">
|
||||
<div class="http-title">业务状态码</div>
|
||||
<div class="http-desc">业务层状态</div>
|
||||
<div class="http-codes">
|
||||
<div class="http-code">
|
||||
<span class="code-num">0</span>
|
||||
<span>业务成功</span>
|
||||
</div>
|
||||
<div class="http-code">
|
||||
<span class="code-num">1xxxx</span>
|
||||
<span>参数错误</span>
|
||||
</div>
|
||||
<div class="http-code">
|
||||
<span class="code-num">2xxxx</span>
|
||||
<span>业务错误</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="http-note">HTTP 200 + 业务错误码 是业界主流做法</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'examples'" class="section">
|
||||
<h4>常见错误码示例</h4>
|
||||
<div class="ex-tabs">
|
||||
<button
|
||||
v-for="ex in examples"
|
||||
:key="ex.id"
|
||||
:class="['ex-tab', { active: exId === ex.id }]"
|
||||
@click="exId = ex.id"
|
||||
>
|
||||
{{ ex.name }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ex-content">
|
||||
<div class="ex-list">
|
||||
<div
|
||||
v-for="item in currentExample.items"
|
||||
:key="item.code"
|
||||
class="ex-row"
|
||||
>
|
||||
<code class="ex-code">{{ item.code }}</code>
|
||||
<span class="ex-msg">{{ item.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
<span class="tips-icon">💡</span>
|
||||
<span class="tips-text">错误信息要"机器可读 + 人类友好",便于前端统一处理</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const active = ref('validate')
|
||||
const exId = ref('param')
|
||||
|
||||
const tabs = [
|
||||
{ id: 'validate', icon: '🔍', name: '参数校验' },
|
||||
{ id: 'business', icon: '💼', name: '业务错误' },
|
||||
{ id: 'layers', icon: '📊', name: '分层设计' },
|
||||
{ id: 'http', icon: '🌐', name: 'HTTP对比' },
|
||||
{ id: 'examples', icon: '📋', name: '常见示例' }
|
||||
]
|
||||
|
||||
const layers = [
|
||||
{ range: '50001-59999', name: '系统层', example: '50001 数据库异常', desc: '基础设施问题' },
|
||||
{ range: '40001-49999', name: '服务层', example: '40001 第三方服务超时', desc: '外部依赖问题' },
|
||||
{ range: '30001-39999', name: '认证层', example: '30001 未登录', desc: '身份权限问题' },
|
||||
{ range: '20001-29999', name: '业务层', example: '20001 余额不足', desc: '业务规则校验' },
|
||||
{ range: '10001-19999', name: '参数层', example: '10001 参数缺失', desc: '客户端输入问题' }
|
||||
]
|
||||
|
||||
const examples = [
|
||||
{
|
||||
id: 'param',
|
||||
name: '参数层',
|
||||
items: [
|
||||
{ code: 10001, message: '缺少必填参数' },
|
||||
{ code: 10002, message: '参数格式错误' },
|
||||
{ code: 10003, message: '参数长度超限' },
|
||||
{ code: 10004, message: '参数值非法' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'auth',
|
||||
name: '认证层',
|
||||
items: [
|
||||
{ code: 30001, message: '未登录' },
|
||||
{ code: 30002, message: '登录已过期' },
|
||||
{ code: 30003, message: '无权限访问' },
|
||||
{ code: 30004, message: '账号已被禁用' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'biz',
|
||||
name: '业务层',
|
||||
items: [
|
||||
{ code: 20001, message: '余额不足' },
|
||||
{ code: 20002, message: '商品已下架' },
|
||||
{ code: 20003, message: '订单已取消' },
|
||||
{ code: 20004, message: '库存不足' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sys',
|
||||
name: '系统层',
|
||||
items: [
|
||||
{ code: 50001, message: '数据库异常' },
|
||||
{ code: 50002, message: '缓存服务异常' },
|
||||
{ code: 50003, message: '系统繁忙,请稍后重试' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const currentExample = computed(() => {
|
||||
return examples.find(e => e.id === exId.value) || examples[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 14px 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.field-tips {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tip-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.tip-row code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-brand);
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.tip-row span {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.business-tips {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.b-tip {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 6px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.layer-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.layer-item {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.layer-item {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.layer-range {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layer-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.layer-example {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.layer-desc {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-2);
|
||||
background: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.layer-note {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.http-compare {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.http-compare {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.http-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.http-col {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.http-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.http-desc {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.http-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.http-codes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.http-code {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.code-num {
|
||||
font-family: monospace;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.http-note {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 8px 12px;
|
||||
background: color-mix(in srgb, #22c55e 10%, var(--vp-c-bg));
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ex-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ex-tab {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.ex-tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.ex-tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ex-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ex-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ex-code {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ex-msg {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: var(--vp-c-bg);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.tips-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,603 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="header">
|
||||
<span class="icon">📋</span>
|
||||
<span class="title">API 响应结构设计</span>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:class="['tab', { active: active === tab.id }]"
|
||||
@click="active = tab.id"
|
||||
>
|
||||
{{ tab.icon }} {{ tab.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div v-if="active === 'why'" class="section">
|
||||
<h4>为什么要统一响应格式?</h4>
|
||||
<div class="problem-box">
|
||||
<div class="problem-title">❌ 问题:不同接口返回格式不一致</div>
|
||||
<pre class="code-sm">
|
||||
// 接口 A
|
||||
{ "data": { "user": {...} } }
|
||||
|
||||
// 接口 B
|
||||
{ "result": { "user": {...} } }
|
||||
|
||||
// 接口 C
|
||||
{ "user": {...} }</pre>
|
||||
<div class="problem-desc">
|
||||
前端需要针对每个接口单独处理,代码冗余,容易出错
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-box">
|
||||
<div class="solution-title">✅ 解决:统一响应格式</div>
|
||||
<pre class="code-sm">
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": { ... },
|
||||
"request_id": "req-xxx"
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'fields'" class="section">
|
||||
<h4>响应字段说明</h4>
|
||||
<div class="field-list">
|
||||
<div v-for="field in fields" :key="field.name" class="field-item">
|
||||
<div class="field-header">
|
||||
<code class="field-name">{{ field.name }}</code>
|
||||
<span class="field-type">{{ field.type }}</span>
|
||||
<span v-if="field.required" class="field-required">必填</span>
|
||||
</div>
|
||||
<div class="field-desc">{{ field.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'codes'" class="section">
|
||||
<h4>业务状态码设计</h4>
|
||||
<div class="code-ranges">
|
||||
<div class="range-item">
|
||||
<span class="range-num">0</span>
|
||||
<span class="range-label">成功</span>
|
||||
</div>
|
||||
<div class="range-item">
|
||||
<span class="range-num">1xxxx</span>
|
||||
<span class="range-label">客户端错误</span>
|
||||
</div>
|
||||
<div class="range-item">
|
||||
<span class="range-num">2xxxx</span>
|
||||
<span class="range-label">业务错误</span>
|
||||
</div>
|
||||
<div class="range-item">
|
||||
<span class="range-num">3xxxx</span>
|
||||
<span class="range-label">认证/权限错误</span>
|
||||
</div>
|
||||
<div class="range-item">
|
||||
<span class="range-num">5xxxx</span>
|
||||
<span class="range-label">系统错误</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="code-examples">
|
||||
<div v-for="code in codeExamples" :key="code.code" class="code-row">
|
||||
<code class="code-value">{{ code.code }}</code>
|
||||
<span class="code-msg">{{ code.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'examples'" class="section">
|
||||
<h4>不同场景响应示例</h4>
|
||||
<div class="example-tabs">
|
||||
<button
|
||||
v-for="ex in examples"
|
||||
:key="ex.id"
|
||||
:class="['ex-tab', { active: exId === ex.id }]"
|
||||
@click="exId = ex.id"
|
||||
>
|
||||
{{ ex.name }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="example-content">
|
||||
<pre class="code-block"><code>{{ currentExample.code }}</code></pre>
|
||||
<div class="example-note">{{ currentExample.note }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="active === 'pagination'" class="section">
|
||||
<h4>分页参数设计</h4>
|
||||
<div class="pg-row">
|
||||
<div class="pg-col">
|
||||
<div class="pg-title">请求参数</div>
|
||||
<div class="pg-params">
|
||||
<div class="pg-item">
|
||||
<code>page</code>
|
||||
<span>页码,从 1 开始</span>
|
||||
</div>
|
||||
<div class="pg-item">
|
||||
<code>page_size</code>
|
||||
<span>每页数量,默认 20</span>
|
||||
</div>
|
||||
<div class="pg-item">
|
||||
<code>sort</code>
|
||||
<span>排序,如 created_desc</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pg-col">
|
||||
<div class="pg-title">响应格式</div>
|
||||
<pre class="code-sm">
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total": 156,
|
||||
"total_pages": 8,
|
||||
"has_next": true
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
<span class="tips-icon">💡</span>
|
||||
<span class="tips-text">request_id 用于问题追踪,建议使用 UUID v4 或雪花算法生成</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const active = ref('why')
|
||||
const exId = ref('success')
|
||||
|
||||
const tabs = [
|
||||
{ id: 'why', icon: '❓', name: '为什么统一' },
|
||||
{ id: 'fields', icon: '📝', name: '字段说明' },
|
||||
{ id: 'codes', icon: '🔢', name: '状态码' },
|
||||
{ id: 'examples', icon: '📄', name: '示例' },
|
||||
{ id: 'pagination', icon: '📑', name: '分页' }
|
||||
]
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: 'code',
|
||||
type: 'number',
|
||||
required: true,
|
||||
desc: '业务状态码,0 表示成功'
|
||||
},
|
||||
{ name: 'message', type: 'string', required: true, desc: '状态描述信息' },
|
||||
{
|
||||
name: 'data',
|
||||
type: 'any',
|
||||
required: false,
|
||||
desc: '业务数据,失败时可为 null'
|
||||
},
|
||||
{
|
||||
name: 'request_id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
desc: '请求唯一标识,用于追踪'
|
||||
},
|
||||
{
|
||||
name: 'timestamp',
|
||||
type: 'string',
|
||||
required: false,
|
||||
desc: '响应时间戳,ISO 8601 格式'
|
||||
}
|
||||
]
|
||||
|
||||
const codeExamples = [
|
||||
{ code: 0, message: 'success - 成功' },
|
||||
{ code: 10001, message: '参数错误:缺少必填字段' },
|
||||
{ code: 10002, message: '资源不存在' },
|
||||
{ code: 20001, message: '余额不足' },
|
||||
{ code: 30001, message: '未登录' },
|
||||
{ code: 50001, message: '系统繁忙,请稍后重试' }
|
||||
]
|
||||
|
||||
const examples = [
|
||||
{
|
||||
id: 'success',
|
||||
name: '成功-单对象',
|
||||
code: `{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 123,
|
||||
"name": "张三",
|
||||
"email": "zhangsan@example.com"
|
||||
},
|
||||
"request_id": "req-abc123"
|
||||
}`,
|
||||
note: '成功响应:data 包含具体业务数据'
|
||||
},
|
||||
{
|
||||
id: 'list',
|
||||
name: '成功-列表',
|
||||
code: `{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
{ "id": 1, "name": "商品A" },
|
||||
{ "id": 2, "name": "商品B" }
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total": 156
|
||||
}
|
||||
},
|
||||
"request_id": "req-def456"
|
||||
}`,
|
||||
note: '列表响应:items 数组 + pagination 分页信息'
|
||||
},
|
||||
{
|
||||
id: 'error',
|
||||
name: '业务错误',
|
||||
code: `{
|
||||
"code": 20001,
|
||||
"message": "余额不足,当前余额 50.00 元",
|
||||
"data": null,
|
||||
"request_id": "req-ghi789"
|
||||
}`,
|
||||
note: '业务错误:code 非 0,message 说明原因'
|
||||
},
|
||||
{
|
||||
id: 'validate',
|
||||
name: '参数校验',
|
||||
code: `{
|
||||
"code": 10001,
|
||||
"message": "参数校验失败",
|
||||
"data": {
|
||||
"errors": [
|
||||
{ "field": "email", "message": "邮箱格式不正确" },
|
||||
{ "field": "password", "message": "密码长度至少 8 位" }
|
||||
]
|
||||
},
|
||||
"request_id": "req-jkl012"
|
||||
}`,
|
||||
note: '参数错误:data.errors 列出所有错误字段'
|
||||
}
|
||||
]
|
||||
|
||||
const currentExample = computed(() => {
|
||||
return examples.find((e) => e.id === exId.value) || examples[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 14px 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.problem-box,
|
||||
.solution-box {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.problem-box {
|
||||
background: color-mix(in srgb, #ef4444 8%, var(--vp-c-bg));
|
||||
border: 1px solid color-mix(in srgb, #ef4444 20%, transparent);
|
||||
}
|
||||
|
||||
.solution-box {
|
||||
background: color-mix(in srgb, #22c55e 8%, var(--vp-c-bg));
|
||||
border: 1px solid color-mix(in srgb, #22c55e 20%, transparent);
|
||||
}
|
||||
|
||||
.problem-title,
|
||||
.solution-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.problem-desc {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.code-sm {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.field-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.field-item {
|
||||
padding: 10px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.field-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.field-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.field-type {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.field-required {
|
||||
font-size: 10px;
|
||||
color: #f59e0b;
|
||||
background: color-mix(in srgb, #f59e0b 15%, transparent);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.field-desc {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.code-ranges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.range-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.range-num {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.range-label {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.code-examples {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.code-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.code-msg {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.example-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ex-tab {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.ex-tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.ex-tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Menlo', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.example-note {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 8px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.pg-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pg-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.pg-col {
|
||||
padding: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.pg-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pg-params {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pg-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.pg-item code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.pg-item span {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: var(--vp-c-bg);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.tips-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,417 @@
|
||||
<template>
|
||||
<div class="raf-root">
|
||||
<div class="raf-layout">
|
||||
<!-- Left: Client Side -->
|
||||
<div class="raf-left">
|
||||
<div class="raf-header">
|
||||
<span class="raf-icon">💻</span>
|
||||
<span class="raf-title">Client (Browser/App)</span>
|
||||
</div>
|
||||
|
||||
<div class="raf-controls">
|
||||
<div class="raf-scenarios">
|
||||
<button
|
||||
v-for="s in scenarios"
|
||||
:key="s.id"
|
||||
:class="['raf-chip', { active: currentScenario.id === s.id }]"
|
||||
@click="selectScenario(s)"
|
||||
:disabled="processing"
|
||||
>
|
||||
{{ s.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="raf-request-box">
|
||||
<div class="raf-http-line">
|
||||
<span :class="['raf-method', currentScenario.method]">{{ currentScenario.method }}</span>
|
||||
<span class="raf-url">{{ currentScenario.url }}</span>
|
||||
</div>
|
||||
<div v-if="currentScenario.body" class="raf-code-block">
|
||||
{{ JSON.stringify(currentScenario.body, null, 2) }}
|
||||
</div>
|
||||
<button
|
||||
class="raf-send-btn"
|
||||
@click="sendRequest"
|
||||
:disabled="processing"
|
||||
>
|
||||
{{ processing ? 'Sending...' : 'Send Request' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="raf-response-box" v-if="response">
|
||||
<div class="raf-status-line">
|
||||
<span class="raf-label">Response Status:</span>
|
||||
<span :class="['raf-status-badge', getStatusColor(response.status)]">
|
||||
{{ response.status }} {{ response.statusText }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="raf-code-block response-body">
|
||||
{{ JSON.stringify(response.body, null, 2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Server Side -->
|
||||
<div class="raf-right">
|
||||
<div class="raf-header server-header">
|
||||
<span class="raf-icon">☁️</span>
|
||||
<span class="raf-title">Server (API)</span>
|
||||
</div>
|
||||
|
||||
<div class="raf-server-state">
|
||||
<!-- Database View -->
|
||||
<div class="raf-section">
|
||||
<div class="raf-section-title">📦 Database (Users Resource)</div>
|
||||
<div class="raf-db-view">
|
||||
<transition-group name="list">
|
||||
<div v-for="user in db" :key="user.id" class="raf-db-item">
|
||||
<span class="raf-db-id">ID: {{ user.id }}</span>
|
||||
<span class="raf-db-name">{{ user.name }}</span>
|
||||
<span class="raf-db-role">({{ user.role }})</span>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div v-if="db.length === 0" class="raf-empty">No users found</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs -->
|
||||
<div class="raf-section">
|
||||
<div class="raf-section-title">📜 Server Logs</div>
|
||||
<div class="raf-logs" ref="logsRef">
|
||||
<div v-for="(log, i) in logs" :key="i" class="raf-log-line">
|
||||
<span class="raf-log-time">[{{ log.time }}]</span>
|
||||
<span :class="log.type">{{ log.msg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
|
||||
const processing = ref(false)
|
||||
const response = ref(null)
|
||||
const logs = ref([])
|
||||
const logsRef = ref(null)
|
||||
|
||||
const db = ref([
|
||||
{ id: 1, name: "Alice", role: "admin" },
|
||||
{ id: 2, name: "Bob", role: "user" }
|
||||
])
|
||||
|
||||
const scenarios = [
|
||||
{ id: 'get-all', label: 'GET /users', method: 'GET', url: '/api/users', body: null },
|
||||
{ id: 'get-one', label: 'GET /users/1', method: 'GET', url: '/api/users/1', body: null },
|
||||
{ id: 'create', label: 'POST /users', method: 'POST', url: '/api/users', body: { name: "Charlie", role: "user" } },
|
||||
{ id: 'not-found', label: 'GET /users/99', method: 'GET', url: '/api/users/99', body: null },
|
||||
{ id: 'delete', label: 'DELETE /users/1', method: 'DELETE', url: '/api/users/1', body: null },
|
||||
]
|
||||
|
||||
const currentScenario = ref(scenarios[0])
|
||||
|
||||
function selectScenario(s) {
|
||||
currentScenario.value = s
|
||||
response.value = null
|
||||
}
|
||||
|
||||
function addLog(msg, type = 'info') {
|
||||
const now = new Date()
|
||||
const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
|
||||
logs.value.push({ time, msg, type })
|
||||
nextTick(() => {
|
||||
if (logsRef.value) logsRef.value.scrollTop = logsRef.value.scrollHeight
|
||||
})
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
if (status >= 200 && status < 300) return 'status-success'
|
||||
if (status >= 400 && status < 500) return 'status-error'
|
||||
return 'status-neutral'
|
||||
}
|
||||
|
||||
async function sendRequest() {
|
||||
processing.value = true
|
||||
response.value = null
|
||||
addLog(`Received ${currentScenario.value.method} ${currentScenario.value.url}`, 'info')
|
||||
|
||||
await new Promise(r => setTimeout(r, 600)) // Simulate network latency
|
||||
|
||||
const { method, url, body } = currentScenario.value
|
||||
|
||||
// Router Logic Simulation
|
||||
if (method === 'GET' && url === '/api/users') {
|
||||
response.value = { status: 200, statusText: 'OK', body: db.value }
|
||||
addLog('Matched route: GET /users -> listUsers()', 'success')
|
||||
}
|
||||
else if (method === 'GET' && url.match(/\/api\/users\/\d+/)) {
|
||||
const id = parseInt(url.split('/').pop())
|
||||
const user = db.value.find(u => u.id === id)
|
||||
if (user) {
|
||||
response.value = { status: 200, statusText: 'OK', body: user }
|
||||
addLog(`Found user ${id}`, 'success')
|
||||
} else {
|
||||
response.value = { status: 404, statusText: 'Not Found', body: { error: "User not found" } }
|
||||
addLog(`User ${id} not found in DB`, 'error')
|
||||
}
|
||||
}
|
||||
else if (method === 'POST' && url === '/api/users') {
|
||||
const newUser = { id: Math.max(0, ...db.value.map(u => u.id)) + 1, ...body }
|
||||
db.value.push(newUser)
|
||||
response.value = { status: 201, statusText: 'Created', body: newUser }
|
||||
addLog(`Created user ${newUser.id}`, 'success')
|
||||
}
|
||||
else if (method === 'DELETE' && url.match(/\/api\/users\/\d+/)) {
|
||||
const id = parseInt(url.split('/').pop())
|
||||
const idx = db.value.findIndex(u => u.id === id)
|
||||
if (idx !== -1) {
|
||||
db.value.splice(idx, 1)
|
||||
response.value = { status: 204, statusText: 'No Content', body: null }
|
||||
addLog(`Deleted user ${id}`, 'success')
|
||||
} else {
|
||||
response.value = { status: 404, statusText: 'Not Found', body: { error: "User not found" } }
|
||||
addLog(`User ${id} not found for deletion`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
processing.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.raf-root {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 1rem 0;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.raf-layout {
|
||||
display: flex;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.raf-left, .raf-right {
|
||||
flex: 1;
|
||||
padding: 1.2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.raf-left {
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.raf-right {
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.raf-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
font-size: 1.1em;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.raf-scenarios {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.raf-chip {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.raf-chip:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.raf-chip.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.raf-request-box {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.raf-http-line {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-family: monospace;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.raf-method {
|
||||
font-weight: bold;
|
||||
}
|
||||
.raf-method.GET { color: #61affe; }
|
||||
.raf-method.POST { color: #49cc90; }
|
||||
.raf-method.DELETE { color: #f93e3e; }
|
||||
|
||||
.raf-code-block {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.raf-send-btn {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.raf-send-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.raf-send-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.raf-response-box {
|
||||
margin-top: auto;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 1rem;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.raf-status-line {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.raf-status-badge {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.status-success { background: #d1fae5; color: #065f46; }
|
||||
.status-error { background: #fee2e2; color: #991b1b; }
|
||||
.status-neutral { background: #f3f4f6; color: #374151; }
|
||||
|
||||
.raf-db-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.raf-db-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.raf-db-id { color: var(--vp-c-text-3); font-family: monospace; }
|
||||
.raf-db-name { font-weight: bold; }
|
||||
.raf-db-role { color: var(--vp-c-brand); font-size: 0.9em; }
|
||||
|
||||
.raf-logs {
|
||||
height: 180px;
|
||||
overflow-y: auto;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.raf-log-line {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.raf-log-time { color: #6b7280; flex-shrink: 0; }
|
||||
.info { color: #93c5fd; }
|
||||
.success { color: #86efac; }
|
||||
.error { color: #fca5a5; }
|
||||
|
||||
.raf-section-title {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.raf-section:first-child .raf-section-title { margin-top: 0; }
|
||||
|
||||
.raf-empty {
|
||||
color: var(--vp-c-text-3);
|
||||
font-style: italic;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.raf-layout { flex-direction: column; }
|
||||
.raf-left { border-right: none; border-bottom: 1px solid var(--vp-c-divider); }
|
||||
}
|
||||
</style>
|
||||
+30
-46
@@ -14,30 +14,15 @@
|
||||
>
|
||||
<h5>传统部署</h5>
|
||||
<div class="server-stack">
|
||||
<div class="layer-item app">
|
||||
应用 A
|
||||
</div>
|
||||
<div
|
||||
v-if="showConflict"
|
||||
class="layer-item conflict"
|
||||
>
|
||||
依赖冲突!
|
||||
</div>
|
||||
<div class="layer-item deps">
|
||||
依赖库 v1.0
|
||||
</div>
|
||||
<div class="layer-item os">
|
||||
操作系统
|
||||
</div>
|
||||
<div class="layer-item hardware">
|
||||
物理服务器
|
||||
</div>
|
||||
<div class="layer-item app">应用 A</div>
|
||||
<div v-if="showConflict" class="layer-item conflict">依赖冲突!</div>
|
||||
<div class="layer-item deps">依赖库 v1.0</div>
|
||||
<div class="layer-item os">操作系统</div>
|
||||
<div class="layer-item hardware">物理服务器</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vs-divider">
|
||||
VS
|
||||
</div>
|
||||
<div class="vs-divider">VS</div>
|
||||
|
||||
<div
|
||||
class="layer docker"
|
||||
@@ -48,31 +33,17 @@
|
||||
<div class="docker-stack">
|
||||
<div class="containers">
|
||||
<div class="container-box">
|
||||
<div class="container-app">
|
||||
应用 A
|
||||
</div>
|
||||
<div class="container-deps">
|
||||
依赖 v1.0
|
||||
</div>
|
||||
<div class="container-app">应用 A</div>
|
||||
<div class="container-deps">依赖 v1.0</div>
|
||||
</div>
|
||||
<div class="container-box">
|
||||
<div class="container-app">
|
||||
应用 B
|
||||
</div>
|
||||
<div class="container-deps">
|
||||
依赖 v2.0
|
||||
</div>
|
||||
<div class="container-app">应用 B</div>
|
||||
<div class="container-deps">依赖 v2.0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docker-engine">
|
||||
Docker Engine
|
||||
</div>
|
||||
<div class="host-os">
|
||||
宿主机操作系统
|
||||
</div>
|
||||
<div class="hardware">
|
||||
物理服务器
|
||||
</div>
|
||||
<div class="docker-engine">Docker Engine</div>
|
||||
<div class="host-os">宿主机操作系统</div>
|
||||
<div class="hardware">物理服务器</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,9 +81,17 @@ const showDocker = ref(false)
|
||||
const showConflict = ref(false)
|
||||
|
||||
const benefits = [
|
||||
{ icon: '📦', title: '环境一致性', desc: '开发、测试、生产环境完全一致,告别"在我机器上能跑"' },
|
||||
{
|
||||
icon: '📦',
|
||||
title: '环境一致性',
|
||||
desc: '开发、测试、生产环境完全一致,告别"在我机器上能跑"'
|
||||
},
|
||||
{ icon: '🚀', title: '快速部署', desc: '秒级启动,镜像分发,滚动更新无停机' },
|
||||
{ icon: '📊', title: '资源隔离', desc: 'CPU/内存限制,互不干扰,一台机器跑多个应用' },
|
||||
{
|
||||
icon: '📊',
|
||||
title: '资源隔离',
|
||||
desc: 'CPU/内存限制,互不干扰,一台机器跑多个应用'
|
||||
},
|
||||
{ icon: '🔄', title: '版本管理', desc: '镜像版本化,随时回滚,灰度发布' }
|
||||
]
|
||||
</script>
|
||||
@@ -219,8 +198,13 @@ const benefits = [
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.containers {
|
||||
|
||||
@@ -1,164 +1,82 @@
|
||||
<template>
|
||||
<div class="adder-demo">
|
||||
<div class="demo-header">
|
||||
<span class="icon">🧮</span>
|
||||
<span class="title">加法器:CPU 怎么做加法?</span>
|
||||
<span class="subtitle">从手算竖式理解"逐位计算"的原理</span>
|
||||
<div class="demo-label">二进制加法器 ── 输入 0–15 的两个数,观察逐位计算过程</div>
|
||||
|
||||
<div class="control-row">
|
||||
<label class="input-group">
|
||||
<span class="input-label">A</span>
|
||||
<input v-model.number="inputA" type="number" min="0" max="15" class="num-input" />
|
||||
</label>
|
||||
<span class="op-sign">+</span>
|
||||
<label class="input-group">
|
||||
<span class="input-label">B</span>
|
||||
<input v-model.number="inputB" type="number" min="0" max="15" class="num-input" />
|
||||
</label>
|
||||
<span class="op-sign">=</span>
|
||||
<span class="result-num">{{ resultDec }}</span>
|
||||
</div>
|
||||
|
||||
<div class="intro-section">
|
||||
<div class="intro-title">🎯 先看十进制竖式,理解"逐位计算"</div>
|
||||
<div class="decimal-demo">
|
||||
<div class="decimal-column">
|
||||
<div class="decimal-row label-row">被加数</div>
|
||||
<div class="decimal-row num-row">
|
||||
<span class="d-digit">{{ decimalA }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="decimal-column op-col">
|
||||
<div class="decimal-row label-row">+</div>
|
||||
<div class="decimal-row num-row">
|
||||
<span class="d-digit">{{ decimalB }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="decimal-column">
|
||||
<div class="decimal-row label-row">结果</div>
|
||||
<div class="decimal-row num-row result">
|
||||
<span class="d-digit">{{ decimalA + decimalB }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="binary-display">
|
||||
<div class="binary-row">
|
||||
<span class="binary-label">A</span>
|
||||
<span class="binary-bits">
|
||||
<span v-for="(b, i) in bitsA" :key="'a'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ clampedA }}</span>
|
||||
</div>
|
||||
<div class="intro-hint">
|
||||
<span class="icon">💡</span>
|
||||
<span>手算时,我们从<strong>个位往高位</strong>一位一位算,<strong>逢十进一</strong>。CPU 做加法也一样,只是它只认识 0 和 1,所以要<strong>逢二进一</strong>。</span>
|
||||
<div class="binary-row">
|
||||
<span class="binary-label">B</span>
|
||||
<span class="binary-bits">
|
||||
<span v-for="(b, i) in bitsB" :key="'b'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ clampedB }}</span>
|
||||
</div>
|
||||
<div class="binary-row sum-row">
|
||||
<span class="binary-label">结果</span>
|
||||
<span class="binary-bits">
|
||||
<span v-for="(b, i) in bitsSum" :key="'s'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ fourBitResult }}</span>
|
||||
</div>
|
||||
<div class="bit-labels">
|
||||
<span v-for="i in 4" :key="i" class="bit-label">第{{ 4 - i }}位</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="concept-section">
|
||||
<div class="concept-title">📚 核心概念</div>
|
||||
<div class="concepts-grid">
|
||||
<div class="concept-card half-adder">
|
||||
<div class="concept-name">半加器</div>
|
||||
<div class="concept-simple">只算 A + B</div>
|
||||
<div class="concept-detail">
|
||||
<p>最右边一位用,因为<strong>没有进位进来</strong></p>
|
||||
<p class="formula">输入:A、B → 输出:和(S)、进位(C)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="concept-card full-adder">
|
||||
<div class="concept-name">全加器</div>
|
||||
<div class="concept-simple">算 A + B + 进位</div>
|
||||
<div class="concept-detail">
|
||||
<p>其他位用,因为<strong>要加上一位的进位</strong></p>
|
||||
<p class="formula">输入:A、B、Cin → 输出:和(S)、进位(Cout)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<div class="demo-title">🎮 动手试试:二进制加法</div>
|
||||
<div class="control-row">
|
||||
<label class="input-group">
|
||||
<span class="input-label">A(被加数)</span>
|
||||
<input v-model.number="inputA" type="number" min="0" max="15" class="num-input" />
|
||||
</label>
|
||||
<span class="op-sign">+</span>
|
||||
<label class="input-group">
|
||||
<span class="input-label">B(加数)</span>
|
||||
<input v-model.number="inputB" type="number" min="0" max="15" class="num-input" />
|
||||
</label>
|
||||
<span class="op-sign">=</span>
|
||||
<span class="result-num">{{ resultDec }}</span>
|
||||
</div>
|
||||
|
||||
<div class="binary-display">
|
||||
<div class="binary-row">
|
||||
<span class="binary-label">A</span>
|
||||
<span class="binary-bits">
|
||||
<span v-for="(b, i) in bitsA" :key="'a'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
|
||||
<div class="stages-row">
|
||||
<div
|
||||
v-for="(stage, idx) in stages" :key="idx"
|
||||
class="stage-card"
|
||||
:class="{ active: activeBit === stage.bitPos }"
|
||||
@mouseenter="activeBit = stage.bitPos"
|
||||
@mouseleave="activeBit = null"
|
||||
>
|
||||
<div class="stage-head">
|
||||
<span class="stage-pos">第{{ stage.bitPos }}位</span>
|
||||
<span class="stage-type" :class="stage.carryIn !== null ? 'full' : 'half'">
|
||||
{{ stage.carryIn !== null ? '全加器' : '半加器' }}
|
||||
</span>
|
||||
<span class="binary-dec">= {{ inputA }}</span>
|
||||
</div>
|
||||
<div class="binary-row">
|
||||
<span class="binary-label">B</span>
|
||||
<span class="binary-bits">
|
||||
<span v-for="(b, i) in bitsB" :key="'b'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ inputB }}</span>
|
||||
<div class="stage-io">
|
||||
<span class="io-item"><span class="io-tag a">A</span>{{ stage.a }}</span>
|
||||
<span class="io-item"><span class="io-tag b">B</span>{{ stage.b }}</span>
|
||||
<span v-if="stage.carryIn !== null" class="io-item"><span class="io-tag cin">Cin</span>{{ stage.carryIn }}</span>
|
||||
</div>
|
||||
<div class="binary-row result-row">
|
||||
<span class="binary-label">结果</span>
|
||||
<span class="binary-bits">
|
||||
<span v-for="(b, i) in bitsSum" :key="'s'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ fourBitResult }}</span>
|
||||
</div>
|
||||
<div class="bit-labels">
|
||||
<span v-for="i in 4" :key="i" class="bit-label">第{{ 4 - i }}位</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stages-row">
|
||||
<div
|
||||
v-for="(stage, idx) in stages"
|
||||
:key="idx"
|
||||
class="stage-card"
|
||||
:class="{ active: activeBit === stage.bitPos }"
|
||||
@mouseenter="activeBit = stage.bitPos"
|
||||
@mouseleave="activeBit = null"
|
||||
>
|
||||
<div class="stage-header">
|
||||
<span class="stage-pos">第{{ stage.bitPos }}位</span>
|
||||
<span class="stage-type" :class="stage.carryIn !== null ? 'full' : 'half'">
|
||||
{{ stage.carryIn !== null ? '全加器' : '半加器' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="stage-io">
|
||||
<div class="io-line">
|
||||
<span class="io-tag a">A</span>
|
||||
<span class="io-val">{{ stage.a }}</span>
|
||||
</div>
|
||||
<div class="io-line">
|
||||
<span class="io-tag b">B</span>
|
||||
<span class="io-val">{{ stage.b }}</span>
|
||||
</div>
|
||||
<div v-if="stage.carryIn !== null" class="io-line">
|
||||
<span class="io-tag cin">Cin</span>
|
||||
<span class="io-val">{{ stage.carryIn }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-divider"></div>
|
||||
<div class="stage-io">
|
||||
<div class="io-line">
|
||||
<span class="io-tag s">S</span>
|
||||
<span class="io-val sum">{{ stage.sum }}</span>
|
||||
</div>
|
||||
<div class="io-line">
|
||||
<span class="io-tag cout">Cout</span>
|
||||
<span class="io-val">{{ stage.carryOut }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="idx < 3" class="carry-arrow" :class="{ hasCarry: stage.carryOut }">
|
||||
{{ stage.carryOut ? '→ 进位' : '' }}
|
||||
</div>
|
||||
<div class="stage-divider"></div>
|
||||
<div class="stage-io">
|
||||
<span class="io-item"><span class="io-tag s">S</span><strong>{{ stage.sum }}</strong></span>
|
||||
<span class="io-item"><span class="io-tag cout">C</span>{{ stage.carryOut }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>每位加法器接收 A、B 和上一位的进位,输出本位的和与传给下一位的进位。就像手算竖式"逢二进一",只是用电路自动完成。
|
||||
</div>
|
||||
<div class="demo-caption">鼠标悬停某一位,查看该位加法器的输入 / 输出 · 就像手算竖式"逢二进一"</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const decimalA = 35
|
||||
const decimalB = 47
|
||||
|
||||
const inputA = ref(3)
|
||||
const inputB = ref(2)
|
||||
const activeBit = ref(null)
|
||||
@@ -191,14 +109,7 @@ const stages = computed(() => {
|
||||
sum = (a ^ b) ^ carryIn
|
||||
carryOut = (a & b) | (carryIn & (a ^ b))
|
||||
}
|
||||
result.push({
|
||||
bitPos: i,
|
||||
a,
|
||||
b,
|
||||
carryIn: carryIn === null ? null : carryIn,
|
||||
sum,
|
||||
carryOut
|
||||
})
|
||||
result.push({ bitPos: i, a, b, carryIn: carryIn === null ? null : carryIn, sum, carryOut })
|
||||
carryIn = carryOut
|
||||
}
|
||||
return result
|
||||
@@ -215,7 +126,7 @@ const fourBitResult = computed(() =>
|
||||
|
||||
const overflow = computed(() => clampedA.value + clampedB.value > 15)
|
||||
const resultDec = computed(() =>
|
||||
overflow.value ? `${fourBitResult.value}(4位溢出)` : String(fourBitResult.value)
|
||||
overflow.value ? `${fourBitResult.value}(溢出)` : String(fourBitResult.value)
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -224,164 +135,25 @@ const resultDec = computed(() =>
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.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.25rem; }
|
||||
|
||||
.intro-section {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.decimal-demo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.decimal-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.decimal-column.op-col {
|
||||
min-width: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.decimal-row {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.decimal-row.label-row {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.decimal-row.num-row {
|
||||
font-family: monospace;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.decimal-row.num-row.result {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.d-digit {
|
||||
display: inline-block;
|
||||
min-width: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.intro-hint {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.intro-hint .icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.concept-section {
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.concept-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.concepts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.concept-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
.concept-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.concept-simple {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.concept-detail {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.concept-detail p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.concept-detail .formula {
|
||||
margin-top: 0.2rem;
|
||||
font-family: monospace;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.half-adder .concept-name { color: var(--vp-c-brand-1); }
|
||||
.full-adder .concept-name { color: #8b5cf6; }
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
/* ── controls ── */
|
||||
.control-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
@@ -391,67 +163,70 @@ const resultDec = computed(() =>
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.num-input {
|
||||
width: 3rem;
|
||||
padding: 0.2rem 0.35rem;
|
||||
width: 3.2rem;
|
||||
padding: 0.25rem 0.4rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.9rem;
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.op-sign {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.result-num {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-size: 0.95rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* ── binary ── */
|
||||
.binary-display {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.binary-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
margin-bottom: 0.15rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.binary-label {
|
||||
color: var(--vp-c-text-2);
|
||||
min-width: 2.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.binary-bits {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
font-family: monospace;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.bit {
|
||||
display: inline-block;
|
||||
min-width: 1.2rem;
|
||||
min-width: 1.3rem;
|
||||
text-align: center;
|
||||
padding: 0.1rem 0.15rem;
|
||||
border-radius: 3px;
|
||||
transition: all 0.15s ease;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.bit.highlight {
|
||||
.bit.hl {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
@@ -459,11 +234,11 @@ const resultDec = computed(() =>
|
||||
|
||||
.binary-dec {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.78rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.result-row .binary-bits {
|
||||
.sum-row .binary-bits {
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
@@ -472,30 +247,31 @@ const resultDec = computed(() =>
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
margin-left: 3rem;
|
||||
margin-top: 0.15rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
.bit-label {
|
||||
min-width: 1.2rem;
|
||||
min-width: 1.3rem;
|
||||
text-align: center;
|
||||
font-size: 0.65rem;
|
||||
font-size: 0.6rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
/* ── stages ── */
|
||||
.stages-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stage-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem;
|
||||
padding: 0.45rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
position: relative;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.stage-card.active {
|
||||
@@ -503,25 +279,25 @@ const resultDec = computed(() =>
|
||||
box-shadow: 0 0 0 1px var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.stage-header {
|
||||
.stage-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.25rem;
|
||||
padding-bottom: 0.2rem;
|
||||
margin-bottom: 0.2rem;
|
||||
padding-bottom: 0.15rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.stage-pos {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.stage-type {
|
||||
font-size: 0.65rem;
|
||||
font-size: 0.6rem;
|
||||
font-weight: bold;
|
||||
padding: 0.1rem 0.25rem;
|
||||
padding: 0.08rem 0.25rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@@ -538,22 +314,24 @@ const resultDec = computed(() =>
|
||||
.stage-io {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.io-line {
|
||||
.io-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.2rem;
|
||||
gap: 0.25rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.io-tag {
|
||||
font-size: 0.6rem;
|
||||
font-size: 0.55rem;
|
||||
font-weight: bold;
|
||||
padding: 0.05rem 0.2rem;
|
||||
padding: 0.04rem 0.18rem;
|
||||
border-radius: 2px;
|
||||
color: white;
|
||||
font-family: system-ui;
|
||||
}
|
||||
|
||||
.io-tag.a { background: var(--vp-c-brand-1); }
|
||||
@@ -562,59 +340,21 @@ const resultDec = computed(() =>
|
||||
.io-tag.s { background: var(--vp-c-green-1, #16a34a); }
|
||||
.io-tag.cout { background: #d97706; }
|
||||
|
||||
.io-val {
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.io-val.sum {
|
||||
color: var(--vp-c-green-1, #16a34a);
|
||||
}
|
||||
|
||||
.stage-divider {
|
||||
height: 1px;
|
||||
background: var(--vp-c-divider);
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
|
||||
.carry-arrow {
|
||||
position: absolute;
|
||||
right: -0.5rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 0.6rem;
|
||||
color: #d97706;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.carry-arrow.hasCarry {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.35rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-box .icon {
|
||||
flex-shrink: 0;
|
||||
.demo-caption {
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.stages-row {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.concepts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="cpu-architecture-demo">
|
||||
<div class="demo-label">
|
||||
CPU 核心组件与指令执行周期演示 ── 点击"时钟脉冲"执行一个指令周期
|
||||
</div>
|
||||
|
||||
<div class="cpu-container">
|
||||
<div class="cpu-frame">
|
||||
<h3 class="cpu-title">CPU (中央处理器)</h3>
|
||||
|
||||
<div class="components-grid">
|
||||
<!-- Control Unit -->
|
||||
<div
|
||||
class="cu-box component"
|
||||
:class="{ active: currentStage === 1 || currentStage === 2 }"
|
||||
>
|
||||
<div class="comp-title">控制单元 (CU)</div>
|
||||
<div class="comp-state">{{ cuState }}</div>
|
||||
</div>
|
||||
|
||||
<!-- ALU -->
|
||||
<div
|
||||
class="alu-box component"
|
||||
:class="{ active: currentStage === 3 }"
|
||||
>
|
||||
<div class="comp-title">算术逻辑单元 (ALU)</div>
|
||||
<div class="comp-state">{{ aluState }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Registers -->
|
||||
<div
|
||||
class="reg-box component"
|
||||
:class="{ active: currentStage === 4 }"
|
||||
>
|
||||
<div class="comp-title">寄存器组</div>
|
||||
<div class="reg-list">
|
||||
<span class="reg">R0: {{ r0 }}</span>
|
||||
<span class="reg">R1: {{ r1 }}</span>
|
||||
<span class="reg">PC: {{ pc }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Memory -->
|
||||
<div
|
||||
class="mem-frame component"
|
||||
:class="{ active: currentStage === 1 || currentStage === 4 }"
|
||||
>
|
||||
<h3 class="cpu-title">内存 (Memory)</h3>
|
||||
<div class="mem-list">
|
||||
<div class="mem-loc" :class="{ 'hl-mem': pc === 10 }">
|
||||
<span class="addr">M[10]</span> 取指:LOAD R0, #5
|
||||
</div>
|
||||
<div class="mem-loc" :class="{ 'hl-mem': pc === 11 }">
|
||||
<span class="addr">M[11]</span> 译码:ADD R1, R0
|
||||
</div>
|
||||
<div class="mem-loc" :class="{ 'hl-mem': pc === 12 }">
|
||||
<span class="addr">M[12]</span> 执行:ALU 计算
|
||||
</div>
|
||||
<div class="mem-loc" :class="{ 'hl-mem': pc === 13 }">
|
||||
<span class="addr">M[13]</span> 写回:将结果保存
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stages progress -->
|
||||
<div class="pipeline">
|
||||
<div class="stage" :class="{ active: currentStage === 1 }">
|
||||
<span class="step-num">1. Fetch</span>
|
||||
<span class="step-desc">取指</span>
|
||||
</div>
|
||||
<div class="stage" :class="{ active: currentStage === 2 }">
|
||||
<span class="step-num">2. Decode</span>
|
||||
<span class="step-desc">译码</span>
|
||||
</div>
|
||||
<div class="stage" :class="{ active: currentStage === 3 }">
|
||||
<span class="step-num">3. Execute</span>
|
||||
<span class="step-desc">执行</span>
|
||||
</div>
|
||||
<div class="stage" :class="{ active: currentStage === 4 }">
|
||||
<span class="step-num">4. WriteBack</span>
|
||||
<span class="step-desc">写回</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="clock-btn" @click="nextStage">
|
||||
<span class="clock-icon">⟳</span> 给一个时钟脉冲 (Next Stage)
|
||||
</button>
|
||||
<button class="reset-btn" @click="reset">重置</button>
|
||||
</div>
|
||||
|
||||
<div class="logic-explain">
|
||||
<p>
|
||||
当前阶段状态:<strong>{{ statusMsg }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const currentStage = ref(0) // 0 = Idle, 1 = Fetch, 2 = Decode, 3 = Execute, 4 = Writeback
|
||||
const cycleCount = ref(0)
|
||||
|
||||
const pc = ref(10)
|
||||
const r0 = ref(0)
|
||||
const r1 = ref(0)
|
||||
|
||||
const cuState = ref('等待时钟信号...')
|
||||
const aluState = ref('空闲')
|
||||
|
||||
const instructions = [
|
||||
'LOAD R0, #5',
|
||||
'LOAD R1, #3',
|
||||
'ADD R0, R1',
|
||||
'STORE M[14], R0'
|
||||
]
|
||||
|
||||
const statusMsg = computed(() => {
|
||||
if (currentStage.value === 0)
|
||||
return '系统启动,等待接收时钟脉冲开始运行程序。'
|
||||
if (currentStage.value === 1)
|
||||
return `CPU 内部的控制单元根据程序计数器 (PC=${pc.value}),从内存取出当前指令。`
|
||||
if (currentStage.value === 2)
|
||||
return `控制单元翻译指令为硬件控制信号:准备执行操作。`
|
||||
if (currentStage.value === 3)
|
||||
return `ALU 进行计算或控制流转移,当前在处理实际数据...`
|
||||
if (currentStage.value === 4)
|
||||
return `将运算结果写入寄存器组或写回内存,更新程序计数器(PC)。`
|
||||
return ''
|
||||
})
|
||||
|
||||
function nextStage() {
|
||||
if (currentStage.value === 0 || currentStage.value === 4) {
|
||||
currentStage.value = 1
|
||||
cuState.value = `取指: 读取指令`
|
||||
aluState.value = '空闲'
|
||||
if (currentStage.value === 4) pc.value++
|
||||
} else if (currentStage.value === 1) {
|
||||
currentStage.value = 2
|
||||
cuState.value = `译码: 准备相关电路`
|
||||
} else if (currentStage.value === 2) {
|
||||
currentStage.value = 3
|
||||
cuState.value = '等待 ALU 结果'
|
||||
aluState.value = '计算进行中...'
|
||||
} else if (currentStage.value === 3) {
|
||||
currentStage.value = 4
|
||||
cuState.value = '完成'
|
||||
aluState.value = '结果输出'
|
||||
// Fake logic update
|
||||
if (cycleCount.value === 0) r0.value = 5
|
||||
if (cycleCount.value === 1) r1.value = 3
|
||||
if (cycleCount.value === 2) r0.value = r0.value + r1.value
|
||||
cycleCount.value++
|
||||
if (pc.value >= 13) {
|
||||
pc.value = 9
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
currentStage.value = 0
|
||||
cycleCount.value = 0
|
||||
pc.value = 10
|
||||
r0.value = 0
|
||||
r1.value = 0
|
||||
cuState.value = '等待时钟信号...'
|
||||
aluState.value = '空闲'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cpu-architecture-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.cpu-container {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.cpu-frame {
|
||||
flex: 2;
|
||||
border: 2px dashed var(--vp-c-brand-1);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.mem-frame {
|
||||
flex: 1;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.cpu-title {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand-1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.components-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.component {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 0.8rem;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.component.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.comp-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.comp-state {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-1);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.reg-list {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.reg {
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.mem-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.mem-loc {
|
||||
padding: 0.3rem 0.5rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.mem-loc.hl-mem {
|
||||
background: #fef08a;
|
||||
color: #a16207;
|
||||
border-color: #a16207;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.addr {
|
||||
color: var(--vp-c-text-3);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Pipeline Stages */
|
||||
.pipeline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stage {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 0.5rem 0;
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.stage:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.stage.active {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
.step-desc {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.clock-btn,
|
||||
.reset-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clock-btn {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
.clock-btn:hover {
|
||||
background: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background: transparent;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.logic-explain {
|
||||
margin-top: 1rem;
|
||||
padding: 0.8rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.cpu-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+266
-255
@@ -1,303 +1,314 @@
|
||||
<template>
|
||||
<div class="filesystem-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">文件系统:数据的"档案柜"</span>
|
||||
<span class="subtitle">操作系统如何组织和管理文件</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="fs-tree">
|
||||
<div class="tree-header">
|
||||
<span class="header-icon">📂</span>
|
||||
<span>目录结构</span>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
<div
|
||||
v-for="item in fileTree"
|
||||
:key="item.path"
|
||||
class="tree-item"
|
||||
:class="{ selected: selectedItem === item.path }"
|
||||
:style="{ paddingLeft: (item.level * 12) + 'px' }"
|
||||
@click="selectItem(item)"
|
||||
>
|
||||
<span class="item-icon">{{ item.icon }}</span>
|
||||
<span class="item-name">{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fs-detail">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ selectedItemInfo?.icon }}</span>
|
||||
<span class="detail-name">{{ selectedItemInfo?.name }}</span>
|
||||
<div class="demo-wrapper">
|
||||
|
||||
<!-- 文件树:逻辑视角 -->
|
||||
<div class="logical-view">
|
||||
<div class="view-title">
|
||||
<span>📁 你的视角 (文件系统)</span>
|
||||
<span class="subtitle">漂亮、整洁的目录树</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedItemInfo"
|
||||
class="detail-info"
|
||||
>
|
||||
<div class="info-row">
|
||||
<span class="info-label">类型</span>
|
||||
<span class="info-value">{{ selectedItemInfo.type }}</span>
|
||||
<div class="file-tree">
|
||||
<div class="tree-node folder expanded">
|
||||
<span class="icon">💾</span> D盘 (根目录)
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">路径</span>
|
||||
<span class="info-value">{{ selectedItemInfo.path }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedItemInfo.type === '文件'"
|
||||
class="info-row"
|
||||
>
|
||||
<span class="info-label">大小</span>
|
||||
<span class="info-value">{{ selectedItemInfo.size }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">权限</span>
|
||||
<span class="info-value">{{ selectedItemInfo.permission }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedItemInfo?.type === '文件'"
|
||||
class="inode-info"
|
||||
>
|
||||
<div class="inode-title">
|
||||
inode 信息
|
||||
</div>
|
||||
<div class="inode-visual">
|
||||
<div class="inode-block">
|
||||
<span class="inode-label">inode 编号</span>
|
||||
<span class="inode-value">{{ selectedItemInfo.inode }}</span>
|
||||
<div class="tree-children">
|
||||
|
||||
<div class="tree-node folder expanded">
|
||||
<span class="icon">📂</span> 照片
|
||||
</div>
|
||||
<div class="inode-block">
|
||||
<span class="inode-label">数据块</span>
|
||||
<div class="data-blocks">
|
||||
<span
|
||||
v-for="b in selectedItemInfo.blocks"
|
||||
:key="b"
|
||||
class="block"
|
||||
>{{ b }}</span>
|
||||
<div class="tree-children">
|
||||
<div
|
||||
class="tree-node file"
|
||||
:class="{ active: activeFile === 'pet' }"
|
||||
@click="selectFile('pet')"
|
||||
>
|
||||
<span class="icon">🖼️</span> 宠物.jpg
|
||||
<span class="size-badge">3 块</span>
|
||||
</div>
|
||||
<div
|
||||
class="tree-node file"
|
||||
:class="{ active: activeFile === 'vacation' }"
|
||||
@click="selectFile('vacation')"
|
||||
>
|
||||
<span class="icon">🖼️</span> 旅游.png
|
||||
<span class="size-badge">2 块</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tree-node folder expanded">
|
||||
<span class="icon">📂</span> 工作
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div
|
||||
class="tree-node file"
|
||||
:class="{ active: activeFile === 'doc' }"
|
||||
@click="selectFile('doc')"
|
||||
>
|
||||
<span class="icon">📄</span> 总结.docx
|
||||
<span class="size-badge">4 块</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 翻译官动画 -->
|
||||
<div class="translator">
|
||||
<div class="arrow"></div>
|
||||
<div class="badge">文件系统账本<br/>(inode表)</div>
|
||||
<div class="arrow"></div>
|
||||
</div>
|
||||
|
||||
<!-- 磁盘块:物理视角 -->
|
||||
<div class="physical-view">
|
||||
<div class="view-title">
|
||||
<span>🖨️ 硬盘的视角 (物理存储)</span>
|
||||
<span class="subtitle">无序、零散的数据块</span>
|
||||
</div>
|
||||
|
||||
<div class="disk-grid">
|
||||
<div
|
||||
v-for="block in 24"
|
||||
:key="block"
|
||||
class="disk-block"
|
||||
:class="[
|
||||
getBlockOwner(block),
|
||||
{ active: isBlockActive(block) }
|
||||
]"
|
||||
>
|
||||
{{ block }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="explanation-box" v-if="activeFile">
|
||||
<span v-if="activeFile === 'pet'">
|
||||
💡 宠物.jpg 其实被切碎分别放在了第 3、8、14 块。文件系统帮你做好了翻译,你只需双击它!
|
||||
</span>
|
||||
<span v-if="activeFile === 'vacation'">
|
||||
💡 旅游.png 放在了第 5、6 块。
|
||||
</span>
|
||||
<span v-if="activeFile === 'doc'">
|
||||
💡 总结.docx 被分散存放在 10、11、18、22 块,如果没有文件系统,你得自己背下这些数字才能打开文件。
|
||||
</span>
|
||||
</div>
|
||||
<div class="explanation-box default" v-else>
|
||||
☝️ 试着点击左侧的文件,看看它们在硬盘里到底长什么样。
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>文件系统用"目录树"组织文件,用"inode"记录文件元数据。文件名只是给人看的,系统通过 inode 编号找到真正的数据。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedItem = ref('/home')
|
||||
const activeFile = ref(null)
|
||||
|
||||
const fileTree = ref([
|
||||
{ name: '/', path: '/', icon: '📁', level: 0, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'home', path: '/home', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'user', path: '/home/user', icon: '📁', level: 2, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'documents', path: '/home/user/documents', icon: '📁', level: 3, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'report.pdf', path: '/home/user/documents/report.pdf', icon: '📄', level: 4, type: '文件', size: '2.5MB', permission: 'rw-r--r--', inode: 12345, blocks: ['块1', '块2', '块3'] },
|
||||
{ name: 'photos', path: '/home/user/photos', icon: '📁', level: 3, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'vacation.jpg', path: '/home/user/photos/vacation.jpg', icon: '🖼️', level: 4, type: '文件', size: '4.2MB', permission: 'rw-r--r--', inode: 12346, blocks: ['块4', '块5', '块6', '块7'] },
|
||||
{ name: 'etc', path: '/etc', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'config.yml', path: '/etc/config.yml', icon: '⚙️', level: 2, type: '文件', size: '1.2KB', permission: 'rw-r--r--', inode: 10001, blocks: ['块8'] },
|
||||
{ name: 'var', path: '/var', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'log', path: '/var/log', icon: '📁', level: 2, type: '目录', permission: 'rwxr-xr-x' },
|
||||
{ name: 'system.log', path: '/var/log/system.log', icon: '📝', level: 3, type: '文件', size: '128MB', permission: 'rw-r-----', inode: 20001, blocks: ['块9', '块10', '...'] }
|
||||
])
|
||||
// 映射关系伪造
|
||||
const fileMap = {
|
||||
pet: [3, 8, 14],
|
||||
vacation: [5, 6],
|
||||
doc: [10, 11, 18, 22]
|
||||
}
|
||||
|
||||
const selectedItemInfo = computed(() => {
|
||||
return fileTree.value.find(item => item.path === selectedItem.value)
|
||||
})
|
||||
const selectFile = (file) => {
|
||||
activeFile.value = file
|
||||
}
|
||||
|
||||
const selectItem = (item) => {
|
||||
selectedItem.value = item.path
|
||||
const getBlockOwner = (block) => {
|
||||
for (const [key, blocks] of Object.entries(fileMap)) {
|
||||
if (blocks.includes(block)) return `owner-${key}`
|
||||
}
|
||||
return 'empty'
|
||||
}
|
||||
|
||||
const isBlockActive = (block) => {
|
||||
if (!activeFile.value) return false
|
||||
return fileMap[activeFile.value].includes(block)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filesystem-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
.demo-content {
|
||||
.demo-wrapper {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.fs-tree {
|
||||
@media (max-width: 768px) {
|
||||
.demo-wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
.translator {
|
||||
transform: rotate(90deg);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logical-view, .physical-view {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tree-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.tree-content {
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tree-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tree-item:hover {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.tree-item.selected {
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.fs-detail {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.detail-info {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.85rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.inode-info {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.inode-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.inode-visual {
|
||||
.view-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px dashed var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.inode-block {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.inode-label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.inode-value {
|
||||
.view-title span {
|
||||
font-weight: bold;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.view-title .subtitle {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-weight: normal;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
/* File Tree Styles */
|
||||
.file-tree {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.data-blocks {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.block {
|
||||
padding: 0.15rem 0.4rem;
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
.tree-node {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tree-node:hover {
|
||||
background: var(--vp-c-bg-mute);
|
||||
}
|
||||
.tree-node.file.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree-children {
|
||||
padding-left: 1.5rem;
|
||||
border-left: 1px dashed var(--vp-c-divider);
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
.size-badge {
|
||||
margin-left: auto;
|
||||
font-size: 0.7rem;
|
||||
background: var(--vp-c-bg-mute);
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
.tree-node.active .size-badge {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Translator */
|
||||
.translator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.translator .badge {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
.arrow {
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: var(--vp-c-divider);
|
||||
position: relative;
|
||||
}
|
||||
.arrow::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: var(--vp-c-divider) transparent transparent transparent;
|
||||
}
|
||||
|
||||
/* Disk Grid */
|
||||
.disk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.disk-block {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.disk-block.owner-pet { background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.3); }
|
||||
.disk-block.owner-vacation { background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.3); }
|
||||
.disk-block.owner-doc { background: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.3); }
|
||||
|
||||
.disk-block.active {
|
||||
transform: scale(1.1);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
z-index: 2;
|
||||
}
|
||||
.disk-block.owner-pet.active { background: var(--vp-c-success-1); border-color: var(--vp-c-success-1); }
|
||||
.disk-block.owner-vacation.active { background: var(--vp-c-brand-1); border-color: var(--vp-c-brand-1); }
|
||||
.disk-block.owner-doc.active { background: var(--vp-c-warning-1); border-color: var(--vp-c-warning-1); }
|
||||
|
||||
.explanation-box {
|
||||
padding: 1rem;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
border-left: 4px solid var(--vp-c-success-1);
|
||||
border-radius: 0 8px 8px 0;
|
||||
font-size: 0.95rem;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
.explanation-box.default {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-left-color: var(--vp-c-text-3);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateX(-10px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div class="functional-unit-demo">
|
||||
<div class="demo-label">
|
||||
常见功能单元 ── 切换不同模块,查看其实际工作原理
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="tab-btn"
|
||||
:class="{ active: currentTab === tab.id }"
|
||||
@click="currentTab = tab.id"
|
||||
>
|
||||
{{ tab.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<!-- MUX Demo -->
|
||||
<div v-if="currentTab === 'mux'" class="demo-panel">
|
||||
<div class="panel-desc">
|
||||
<strong>多路选择器 (MUX)</strong>:像铁路道岔一样,根据"选择信号"决定让哪一路数据通过。
|
||||
</div>
|
||||
<div class="mux-container">
|
||||
<div class="inputs">
|
||||
<div class="input-line">
|
||||
<span class="label">数据 0 (D0)</span>
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: muxD0 }"
|
||||
@click="muxD0 = !muxD0"
|
||||
>
|
||||
{{ muxD0 ? '1' : '0' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="label">数据 1 (D1)</span>
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: muxD1 }"
|
||||
@click="muxD1 = !muxD1"
|
||||
>
|
||||
{{ muxD1 ? '1' : '0' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mux-chip">
|
||||
<div class="chip-body">MUX</div>
|
||||
<div class="select-pin">
|
||||
<span class="label">选择 (Sel)</span>
|
||||
<button
|
||||
class="select-btn"
|
||||
:class="{ on: muxSel }"
|
||||
@click="muxSel = !muxSel"
|
||||
>
|
||||
{{ muxSel ? '1' : '0' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="outputs">
|
||||
<div class="output-line" :class="{ active: muxResult }">
|
||||
<span class="label">输出 (Out)</span>
|
||||
<span class="out-val">{{ muxResult ? '1' : '0' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logic-explain">
|
||||
<p>
|
||||
当前选择信号为 {{ muxSel ? '1' : '0' }},因此输出等于 数据
|
||||
{{ muxSel ? '1 (D1)' : '0 (D0)' }} 的值:<strong>{{
|
||||
muxResult ? '1' : '0'
|
||||
}}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Decoder Demo -->
|
||||
<div v-if="currentTab === 'decoder'" class="demo-panel">
|
||||
<div class="panel-desc">
|
||||
<strong>译码器 (Decoder)</strong>:将二进制输入转换为特定输出线的激活信号(例如 2位输入可以激活
|
||||
4根输出线中的一根)。
|
||||
</div>
|
||||
<div class="decoder-container">
|
||||
<div class="inputs vertical">
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: decA1 }"
|
||||
@click="decA1 = !decA1"
|
||||
>
|
||||
{{ decA1 ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">A1 (高位)</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: decA0 }"
|
||||
@click="decA0 = !decA0"
|
||||
>
|
||||
{{ decA0 ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">A0 (低位)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="decoder-chip">
|
||||
<div class="chip-body">2-to-4<br />译码器</div>
|
||||
</div>
|
||||
|
||||
<div class="outputs vertical-out">
|
||||
<div class="output-line" :class="{ active: decResult === 0 }">
|
||||
<span class="out-val">{{ decResult === 0 ? '1' : '0' }}</span>
|
||||
<span class="label">Y0 (当输入 00 时)</span>
|
||||
</div>
|
||||
<div class="output-line" :class="{ active: decResult === 1 }">
|
||||
<span class="out-val">{{ decResult === 1 ? '1' : '0' }}</span>
|
||||
<span class="label">Y1 (当输入 01 时)</span>
|
||||
</div>
|
||||
<div class="output-line" :class="{ active: decResult === 2 }">
|
||||
<span class="out-val">{{ decResult === 2 ? '1' : '0' }}</span>
|
||||
<span class="label">Y2 (当输入 10 时)</span>
|
||||
</div>
|
||||
<div class="output-line" :class="{ active: decResult === 3 }">
|
||||
<span class="out-val">{{ decResult === 3 ? '1' : '0' }}</span>
|
||||
<span class="label">Y3 (当输入 11 时)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logic-explain">
|
||||
<p>
|
||||
当前输入为二进制的 {{ decA1 ? '1' : '0'
|
||||
}}{{ decA0 ? '1' : '0' }} (十进制 {{ decResult }}),因此只有
|
||||
<strong>Y{{ decResult }}</strong> 被激活(输出 1)。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const tabs = [
|
||||
{ id: 'mux', name: '多路选择器 (MUX)' },
|
||||
{ id: 'decoder', name: '译码器 (Decoder)' }
|
||||
]
|
||||
|
||||
const currentTab = ref('mux')
|
||||
|
||||
// MUX State
|
||||
const muxD0 = ref(false)
|
||||
const muxD1 = ref(true)
|
||||
const muxSel = ref(false)
|
||||
const muxResult = computed(() => (muxSel.value ? muxD1.value : muxD0.value))
|
||||
|
||||
// Decoder State
|
||||
const decA1 = ref(false)
|
||||
const decA0 = ref(false)
|
||||
const decResult = computed(() => (decA1.value ? 2 : 0) + (decA0.value ? 1 : 0))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.functional-unit-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.85rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* common elements */
|
||||
.toggle-btn {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.toggle-btn.on {
|
||||
background: var(--vp-c-green-soft, #dcfce7);
|
||||
color: var(--vp-c-green-1, #16a34a);
|
||||
border-color: var(--vp-c-green-1, #16a34a);
|
||||
}
|
||||
|
||||
.out-val {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
.output-line.active .out-val {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
.output-line.active .label {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logic-explain {
|
||||
margin-top: 1rem;
|
||||
padding: 0.8rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
/* MUX Layout */
|
||||
.mux-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.mux-chip {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chip-body {
|
||||
width: 4rem;
|
||||
height: 6rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
clip-path: polygon(0 0, 100% 20%, 100% 80%, 0 100%);
|
||||
}
|
||||
|
||||
.select-pin {
|
||||
position: absolute;
|
||||
bottom: -2.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.select-btn {
|
||||
width: 2rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-btn.on {
|
||||
background: #fef08a; /* yellow soft */
|
||||
color: #a16207;
|
||||
border-color: #a16207;
|
||||
}
|
||||
|
||||
/* Decoder Layout */
|
||||
.decoder-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.inputs.vertical,
|
||||
.outputs.vertical-out {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.decoder-chip .chip-body {
|
||||
width: 5rem;
|
||||
height: 8rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
clip-path: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,56 +1,56 @@
|
||||
<template>
|
||||
<div class="logic-gate-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">逻辑门:用开关做运算</span>
|
||||
<div class="demo-label">四种基本逻辑门 ── 真值表一览</div>
|
||||
|
||||
<div class="gates-grid">
|
||||
<div v-for="gate in gates" :key="gate.name" class="gate-card">
|
||||
<div class="gate-name">{{ gate.name }}</div>
|
||||
<div class="gate-rule">{{ gate.rule }}</div>
|
||||
<table class="mini-truth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>A</th>
|
||||
<th v-if="gate.name !== 'NOT'">B</th>
|
||||
<th>结果</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, i) in gate.rows" :key="i">
|
||||
<td>{{ row[0] }}</td>
|
||||
<td v-if="gate.name !== 'NOT'">{{ row[1] }}</td>
|
||||
<td class="result-cell" :class="{ one: row[row.length - 1] === 1 }">{{ row[row.length - 1] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="intro">
|
||||
输入 A、B 只能是 0 或 1;四种门按不同规则输出一个 0 或 1。下面表格列出所有 4 种输入组合的结果。
|
||||
</p>
|
||||
|
||||
<div class="truth-section">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>A</th>
|
||||
<th>B</th>
|
||||
<th>AND</th>
|
||||
<th>OR</th>
|
||||
<th>NOT(A)</th>
|
||||
<th>XOR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in truthRows" :key="`${row.a}-${row.b}`">
|
||||
<td>{{ row.a }}</td>
|
||||
<td>{{ row.b }}</td>
|
||||
<td>{{ row.and }}</td>
|
||||
<td>{{ row.or }}</td>
|
||||
<td>{{ row.not }}</td>
|
||||
<td>{{ row.xor }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="col-meaning">
|
||||
<li><strong>AND</strong>:两个都是 1 才输出 1(像串联:都通才通)</li>
|
||||
<li><strong>OR</strong>:有一个 1 就输出 1(像并联:一通就通)</li>
|
||||
<li><strong>NOT(A)</strong>:对 A 取反,0→1、1→0</li>
|
||||
<li><strong>XOR</strong>:两个不同输出 1,相同输出 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>逻辑门用晶体管的“开关”组合实现这四种运算,复杂计算都由它们组合而成。
|
||||
</div>
|
||||
<div class="demo-caption">所有数字计算都由这四种门的组合实现</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const truthRows = [
|
||||
{ a: 0, b: 0, and: 0, or: 0, not: 1, xor: 0 },
|
||||
{ a: 0, b: 1, and: 0, or: 1, not: 1, xor: 1 },
|
||||
{ a: 1, b: 0, and: 0, or: 1, not: 0, xor: 1 },
|
||||
{ a: 1, b: 1, and: 1, or: 1, not: 0, xor: 0 }
|
||||
const gates = [
|
||||
{
|
||||
name: 'AND',
|
||||
rule: '都为 1 才得 1',
|
||||
rows: [[0,0,0],[0,1,0],[1,0,0],[1,1,1]]
|
||||
},
|
||||
{
|
||||
name: 'OR',
|
||||
rule: '有一个 1 就得 1',
|
||||
rows: [[0,0,0],[0,1,1],[1,0,1],[1,1,1]]
|
||||
},
|
||||
{
|
||||
name: 'NOT',
|
||||
rule: '取反',
|
||||
rows: [[0,1],[1,0]]
|
||||
},
|
||||
{
|
||||
name: 'XOR',
|
||||
rule: '不同才得 1',
|
||||
rows: [[0,0,0],[0,1,1],[1,0,1],[1,1,0]]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -59,82 +59,81 @@ const truthRows = [
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.gates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gate-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gate-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.gate-rule {
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.intro {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin: 0 0 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.truth-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
.mini-truth {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
font-size: 0.88rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
.mini-truth th,
|
||||
.mini-truth td {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
padding: 0.4rem 0.5rem;
|
||||
vertical-align: middle;
|
||||
padding: 0.2rem 0.3rem;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
th {
|
||||
.mini-truth th {
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.col-meaning {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.col-meaning li {
|
||||
margin-bottom: 0.25rem;
|
||||
.result-cell.one {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.col-meaning strong {
|
||||
color: var(--vp-c-text-1);
|
||||
font-variant-numeric: tabular-nums;
|
||||
.demo-caption {
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 0.6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
@media (max-width: 600px) {
|
||||
.gates-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,80 +1,65 @@
|
||||
<template>
|
||||
<div class="memory-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">内存管理:程序的"工作台"</span>
|
||||
<span class="subtitle">操作系统如何分配和管理内存</span>
|
||||
<div class="demo-controls">
|
||||
<button class="allocate-btn wechat" @click="allocate('wechat')" :disabled="!hasFreeSpace">
|
||||
+ 给微信分配数据
|
||||
</button>
|
||||
<button class="allocate-btn game" @click="allocate('game')" :disabled="!hasFreeSpace">
|
||||
+ 给游戏分配数据
|
||||
</button>
|
||||
<button class="reset-btn" @click="reset">
|
||||
↺ 重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="memory-visual">
|
||||
<div class="mem-header">
|
||||
<span>虚拟内存空间 (4GB)</span>
|
||||
<span class="used-info">已用: {{ usedMemory }}MB / 4096MB</span>
|
||||
</div>
|
||||
<div class="mem-blocks">
|
||||
<div
|
||||
v-for="(block, i) in memoryBlocks"
|
||||
:key="i"
|
||||
class="mem-block"
|
||||
:class="{ allocated: block.allocated, selected: selectedBlock === i }"
|
||||
:style="{ height: block.size + '%' }"
|
||||
@click="selectedBlock = i"
|
||||
>
|
||||
<span
|
||||
v-if="block.size > 5"
|
||||
class="block-label"
|
||||
>{{ block.name }}</span>
|
||||
<span
|
||||
v-if="block.size > 8"
|
||||
class="block-size"
|
||||
>{{ block.sizeMB }}MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="memory-info">
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
内存分配策略
|
||||
</div>
|
||||
<div class="strategy-tabs">
|
||||
<button
|
||||
v-for="s in strategies"
|
||||
:key="s.name"
|
||||
:class="['strat-btn', { active: activeStrategy === s.name }]"
|
||||
@click="activeStrategy = s.name"
|
||||
>
|
||||
{{ s.name }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="strategy-desc">
|
||||
{{ currentStrategy.desc }}
|
||||
<div class="system-view">
|
||||
<!-- 虚拟内存试图 -->
|
||||
<div class="virtual-cluster">
|
||||
<div class="process-vm wechat">
|
||||
<div class="title">💬 微信的虚拟内存<br/>(它认为自己独占了空间)</div>
|
||||
<div class="vm-blocks">
|
||||
<div v-for="i in 4" :key="'w'+i" class="block" :class="{ filled: wechatBlocks >= i }">
|
||||
{{ wechatBlocks >= i ? '数据 ' + i : '虚拟空闲' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="section-title">
|
||||
虚拟内存的作用
|
||||
</div>
|
||||
<div class="vm-benefits">
|
||||
<div
|
||||
v-for="b in benefits"
|
||||
:key="b.title"
|
||||
class="benefit-item"
|
||||
>
|
||||
<span class="benefit-icon">{{ b.icon }}</span>
|
||||
<div class="benefit-content">
|
||||
<span class="benefit-title">{{ b.title }}</span>
|
||||
<span class="benefit-desc">{{ b.desc }}</span>
|
||||
</div>
|
||||
<div class="process-vm game">
|
||||
<div class="title">🎮 游戏的虚拟内存<br/>(它也认为自己独占了空间)</div>
|
||||
<div class="vm-blocks">
|
||||
<div v-for="i in 4" :key="'g'+i" class="block" :class="{ filled: gameBlocks >= i }">
|
||||
{{ gameBlocks >= i ? '数据 ' + i : '虚拟空闲' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>虚拟内存让每个进程都以为自己独占整个内存空间,实际由操作系统统一管理和映射,实现隔离和保护。
|
||||
<!-- OS 页表 (映射表) -->
|
||||
<div class="os-page-table">
|
||||
<div class="title">保安大叔 (OS 页表)</div>
|
||||
<div class="table-info">
|
||||
当程序存数据时,<br/>由我暗中转移到真正的物理缝隙里。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 物理内存 -->
|
||||
<div class="physical-memory">
|
||||
<div class="title">🗄️ 真实的物理内存条<br/>(其实像个大杂烩一样乱)</div>
|
||||
<div class="pm-blocks">
|
||||
<div
|
||||
v-for="(block, idx) in physicalBlocks"
|
||||
:key="'p'+idx"
|
||||
class="block"
|
||||
:class="[block.type, { occupied: block.type !== 'empty' }]"
|
||||
>
|
||||
{{ block.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation-box" v-if="wechatBlocks > 0 || gameBlocks > 0">
|
||||
💡 发现了没?尽管右侧真正的物理内存已经被塞得像个狗皮膏药,但在左侧的微信和游戏眼里,自己的内存条永远是连续且干净的。更重要的是,微信绝对访问不到橘色的物理块,保证了安全!
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -82,215 +67,273 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const selectedBlock = ref(0)
|
||||
const activeStrategy = ref('首次适应')
|
||||
const wechatBlocks = ref(0)
|
||||
const gameBlocks = ref(0)
|
||||
|
||||
const memoryBlocks = ref([
|
||||
{ name: '内核空间', size: 25, allocated: true, sizeMB: 1024 },
|
||||
{ name: '进程A', size: 15, allocated: true, sizeMB: 600 },
|
||||
{ name: '空闲', size: 5, allocated: false, sizeMB: 200 },
|
||||
{ name: '进程B', size: 20, allocated: true, sizeMB: 800 },
|
||||
{ name: '空闲', size: 10, allocated: false, sizeMB: 400 },
|
||||
{ name: '进程C', size: 10, allocated: true, sizeMB: 400 },
|
||||
{ name: '空闲', size: 15, allocated: false, sizeMB: 600 }
|
||||
])
|
||||
|
||||
const strategies = [
|
||||
{ name: '首次适应', desc: '从内存开始找,找到第一个足够大的空闲块就分配。速度快,但可能产生小碎片。' },
|
||||
{ name: '最佳适应', desc: '找最小的能满足需求的空闲块。内存利用率高,但可能产生很多小碎片。' },
|
||||
{ name: '最坏适应', desc: '找最大的空闲块分配。减少小碎片,但大块内存很快用完。' }
|
||||
// 初始物理内存状态,模拟碎片化
|
||||
// empty = 空, os = 系统占用
|
||||
const initialPhysicalBlocks = [
|
||||
{ type: 'os', label: '系统核心占用' },
|
||||
{ type: 'empty', label: '空闲' },
|
||||
{ type: 'os', label: '系统保留' },
|
||||
{ type: 'empty', label: '空闲' },
|
||||
{ type: 'empty', label: '空闲' },
|
||||
{ type: 'empty', label: '空闲' },
|
||||
{ type: 'os', label: '系统驱动' },
|
||||
{ type: 'empty', label: '空闲' },
|
||||
]
|
||||
|
||||
const benefits = [
|
||||
{ icon: '🔒', title: '内存隔离', desc: '进程间互不干扰,一个崩溃不影响其他' },
|
||||
{ icon: '📦', title: '内存保护', desc: '防止进程访问不该访问的内存区域' },
|
||||
{ icon: '💾', title: '内存扩展', desc: '用磁盘当内存用,突破物理内存限制' }
|
||||
]
|
||||
const physicalBlocks = ref(JSON.parse(JSON.stringify(initialPhysicalBlocks)))
|
||||
|
||||
const currentStrategy = computed(() => {
|
||||
return strategies.find(s => s.name === activeStrategy.value)
|
||||
const freeSpaceCount = computed(() => {
|
||||
return physicalBlocks.value.filter(b => b.type === 'empty').length
|
||||
})
|
||||
|
||||
const usedMemory = computed(() => {
|
||||
return memoryBlocks.value
|
||||
.filter(b => b.allocated)
|
||||
.reduce((sum, b) => sum + b.sizeMB, 0)
|
||||
})
|
||||
const hasFreeSpace = computed(() => freeSpaceCount.value > 0)
|
||||
|
||||
const allocate = (process) => {
|
||||
if (!hasFreeSpace.value) return
|
||||
|
||||
// Find a process block logic
|
||||
if (process === 'wechat' && wechatBlocks.value < 4) {
|
||||
wechatBlocks.value++
|
||||
fillRandomEmptyBlock('wechat', `微信数据 ${wechatBlocks.value}`)
|
||||
} else if (process === 'game' && gameBlocks.value < 4) {
|
||||
gameBlocks.value++
|
||||
fillRandomEmptyBlock('game', `游戏数据 ${gameBlocks.value}`)
|
||||
}
|
||||
}
|
||||
|
||||
const fillRandomEmptyBlock = (type, label) => {
|
||||
const emptyIndices = []
|
||||
physicalBlocks.value.forEach((b, i) => {
|
||||
if (b.type === 'empty') emptyIndices.push(i)
|
||||
})
|
||||
|
||||
if (emptyIndices.length > 0) {
|
||||
const randomIndex = emptyIndices[Math.floor(Math.random() * emptyIndices.length)]
|
||||
physicalBlocks.value[randomIndex] = { type, label }
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
wechatBlocks.value = 0
|
||||
gameBlocks.value = 0
|
||||
physicalBlocks.value = JSON.parse(JSON.stringify(initialPhysicalBlocks))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.memory-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
.demo-content {
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.memory-visual {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
.allocate-btn {
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.allocate-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.allocate-btn.wechat {
|
||||
background: var(--vp-c-success-1);
|
||||
}
|
||||
.allocate-btn.wechat:not(:disabled):hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.allocate-btn.game {
|
||||
background: var(--vp-c-warning-1);
|
||||
}
|
||||
.allocate-btn.game:not(:disabled):hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.mem-header {
|
||||
.reset-btn {
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-2);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.reset-btn:hover {
|
||||
background: var(--vp-c-bg-mute);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.system-view {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 4px;
|
||||
align-items: stretch;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.used-info {
|
||||
color: var(--vp-c-brand);
|
||||
@media (max-width: 768px) {
|
||||
.system-view {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--vp-c-text-1);
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
.mem-blocks {
|
||||
.virtual-cluster {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.process-vm {
|
||||
flex: 1;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px dashed var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.vm-blocks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
height: 250px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mem-block {
|
||||
.block {
|
||||
padding: 0.6rem;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.process-vm .block {
|
||||
background: var(--vp-c-bg-mute);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.process-vm.wechat .block.filled {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
border: 1px solid var(--vp-c-success-1);
|
||||
color: var(--vp-c-success-1);
|
||||
opacity: 1;
|
||||
}
|
||||
.process-vm.game .block.filled {
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
border: 1px solid var(--vp-c-warning-1);
|
||||
color: var(--vp-c-warning-1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.os-page-table {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
border: 2px solid var(--vp-c-brand-1);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.mem-block.allocated {
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.mem-block:not(.allocated) {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.mem-block.selected {
|
||||
outline: 2px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.block-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.block-size {
|
||||
font-size: 0.65rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.memory-info {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.strategy-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.strat-btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.strat-btn.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.strategy-desc {
|
||||
.os-page-table .table-info {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
text-align: center;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.vm-benefits {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.benefit-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.benefit-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.benefit-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
.physical-memory {
|
||||
flex: 1;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
border: 2px solid var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.pm-blocks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.pm-blocks .block {
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.pm-blocks .block.os {
|
||||
background: var(--vp-c-bg-mute);
|
||||
color: var(--vp-c-text-2);
|
||||
border-style: dashed;
|
||||
}
|
||||
.pm-blocks .block.wechat {
|
||||
background: var(--vp-c-success-1);
|
||||
color: white;
|
||||
border-color: var(--vp-c-success-1);
|
||||
animation: popIn 0.3s ease-out;
|
||||
}
|
||||
.pm-blocks .block.game {
|
||||
background: var(--vp-c-warning-1);
|
||||
color: white;
|
||||
border-color: var(--vp-c-warning-1);
|
||||
animation: popIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
0% { transform: scale(0.9); opacity: 0; }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.explanation-box {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
border-left: 4px solid var(--vp-c-success-1);
|
||||
border-radius: 0 8px 8px 0;
|
||||
font-size: 0.95rem;
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,272 +1,356 @@
|
||||
<template>
|
||||
<div class="process-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">进程:程序的"分身术"</span>
|
||||
<span class="subtitle">一个程序如何同时运行多个实例</span>
|
||||
<div class="controls-section">
|
||||
<button class="action-btn" :class="{ active: isRunning }" @click="toggleSimulation">
|
||||
{{ isRunning ? '⏸ 暂停时间片轮转' : '▶️ 启动 CPU' }}
|
||||
</button>
|
||||
<div class="speed-control">
|
||||
<label>时间流速:</label>
|
||||
<button :class="{ active: speed === 'slow' }" @click="setSpeed('slow')">极慢动作</button>
|
||||
<button :class="{ active: speed === 'fast' }" @click="setSpeed('fast')">真实速度</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="process-list">
|
||||
<div class="process-header">
|
||||
<span class="col-name">进程名</span>
|
||||
<span class="col-pid">PID</span>
|
||||
<span class="col-state">状态</span>
|
||||
<span class="col-mem">内存</span>
|
||||
<div class="cpu-container">
|
||||
<div class="cpu-core" :class="{ active: isRunning }">
|
||||
<div class="cpu-title">单核 CPU</div>
|
||||
<div class="current-task">
|
||||
<span v-if="activeProcess" class="task-badge">
|
||||
正在处理: {{ activeProcess.icon }} {{ activeProcess.name }}
|
||||
</span>
|
||||
<span v-else class="task-badge idle">
|
||||
空闲中...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 连接线动画 -->
|
||||
<div class="connector">
|
||||
<div
|
||||
v-for="p in processes"
|
||||
:key="p.pid"
|
||||
class="process-item"
|
||||
:class="{ running: p.state === '运行中', selected: selectedPid === p.pid }"
|
||||
@click="selectedPid = p.pid"
|
||||
>
|
||||
<span class="col-name">
|
||||
<span class="process-icon">{{ p.icon }}</span>
|
||||
{{ p.name }}
|
||||
</span>
|
||||
<span class="col-pid">{{ p.pid }}</span>
|
||||
<span class="col-state">
|
||||
<span
|
||||
class="state-badge"
|
||||
:class="p.state === '运行中' ? 'running' : 'waiting'"
|
||||
>
|
||||
{{ p.state }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="col-mem">{{ p.memory }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedProcess"
|
||||
class="process-detail"
|
||||
>
|
||||
<div class="detail-title">
|
||||
进程详情:{{ selectedProcess.name }}
|
||||
</div>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="label">进程ID (PID)</span>
|
||||
<span class="value">{{ selectedProcess.pid }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">父进程ID</span>
|
||||
<span class="value">{{ selectedProcess.ppid }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">内存占用</span>
|
||||
<span class="value">{{ selectedProcess.memory }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">CPU 占用</span>
|
||||
<span class="value">{{ selectedProcess.cpu }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memory-layout">
|
||||
<div class="layout-title">
|
||||
进程内存布局
|
||||
</div>
|
||||
<div class="layout-visual">
|
||||
<div
|
||||
v-for="seg in memorySegments"
|
||||
:key="seg.name"
|
||||
class="segment"
|
||||
:style="{ height: seg.height }"
|
||||
>
|
||||
<span class="seg-name">{{ seg.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
class="data-flow"
|
||||
:class="[ `flow-${activeProcessId}`, { running: isRunning }]">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>进程是程序的"运行实例"。同一个程序可以启动多个进程,每个进程有独立的内存空间,互不干扰。
|
||||
<div class="processes-grid">
|
||||
<div
|
||||
v-for="p in processes"
|
||||
:key="p.id"
|
||||
class="process-card"
|
||||
:class="{ active: p.id === activeProcessId }"
|
||||
>
|
||||
<div class="p-header">
|
||||
<div class="p-title">
|
||||
<span class="icon">{{ p.icon }}</span>
|
||||
<span class="name">{{ p.name }}</span>
|
||||
</div>
|
||||
<span class="status-badge" :class="p.id === activeProcessId ? 'running' : 'waiting'">
|
||||
{{ p.id === activeProcessId ? '独占 CPU' : '排队等待' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-progress">
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" :style="{ width: p.progress + '%' }"></div>
|
||||
</div>
|
||||
<div class="progress-text">{{ Math.floor(p.progress) }}% 完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation-box" :class="{ show: isRunning && speed === 'fast' }">
|
||||
💡 **关键启示**:当切换速度足够快时,肉眼已经无法分辨谁在“等待”。这也就是为什么只有一个 CPU 核心的电脑,依然能让你一边听歌一边打字!
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
|
||||
const selectedPid = ref(1001)
|
||||
const isRunning = ref(false)
|
||||
const activeProcessId = ref(null)
|
||||
const speed = ref('slow')
|
||||
let interval = null
|
||||
|
||||
const processes = ref([
|
||||
{ pid: 1001, name: 'Chrome', icon: '🌐', state: '运行中', memory: '512MB', cpu: 15, ppid: 1 },
|
||||
{ pid: 1002, name: 'VS Code', icon: '📝', state: '运行中', memory: '384MB', cpu: 8, ppid: 1 },
|
||||
{ pid: 1003, name: '微信', icon: '💬', state: '等待中', memory: '256MB', cpu: 2, ppid: 1 },
|
||||
{ pid: 1004, name: '终端', icon: '⬛', state: '等待中', memory: '32MB', cpu: 0, ppid: 1002 },
|
||||
{ pid: 1005, name: '音乐', icon: '🎵', state: '运行中', memory: '128MB', cpu: 3, ppid: 1 }
|
||||
{ id: 1, name: '微信接收', icon: '💬', progress: 0 },
|
||||
{ id: 2, name: '音乐播放', icon: '🎵', progress: 0 },
|
||||
{ id: 3, name: '游戏渲染', icon: '🎮', progress: 0 }
|
||||
])
|
||||
|
||||
const selectedProcess = computed(() => {
|
||||
return processes.value.find(p => p.pid === selectedPid.value)
|
||||
})
|
||||
const activeProcess = computed(() => processes.value.find(p => p.id === activeProcessId.value))
|
||||
|
||||
const memorySegments = [
|
||||
{ name: '栈区 (Stack)', height: '20%' },
|
||||
{ name: '堆区 (Heap)', height: '35%' },
|
||||
{ name: '数据段 (Data)', height: '15%' },
|
||||
{ name: '代码段 (Text)', height: '30%' }
|
||||
]
|
||||
const setSpeed = (s) => {
|
||||
speed.value = s
|
||||
if (isRunning.value) {
|
||||
clearInterval(interval)
|
||||
startLoop()
|
||||
}
|
||||
}
|
||||
|
||||
const startLoop = () => {
|
||||
const switchTime = speed.value === 'slow' ? 1200 : 80; // 慢动作 1.2s,快动作极快
|
||||
|
||||
if (!activeProcessId.value) {
|
||||
activeProcessId.value = 1
|
||||
}
|
||||
|
||||
interval = setInterval(() => {
|
||||
// 增加当前进度
|
||||
const curr = processes.value.find(p => p.id === activeProcessId.value)
|
||||
if (curr) {
|
||||
curr.progress += (speed.value === 'slow' ? 15 : 4)
|
||||
if (curr.progress >= 100) curr.progress = 0
|
||||
}
|
||||
|
||||
// 切换下一个
|
||||
let nextId = activeProcessId.value + 1
|
||||
if (nextId > 3) nextId = 1
|
||||
activeProcessId.value = nextId
|
||||
|
||||
}, switchTime)
|
||||
}
|
||||
|
||||
const toggleSimulation = () => {
|
||||
if (isRunning.value) {
|
||||
clearInterval(interval)
|
||||
isRunning.value = false
|
||||
activeProcessId.value = null
|
||||
} else {
|
||||
isRunning.value = true
|
||||
startLoop()
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (interval) clearInterval(interval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.process-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
.controls-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 160px;
|
||||
}
|
||||
.action-btn.active {
|
||||
background: var(--vp-c-danger-1);
|
||||
}
|
||||
.action-btn:hover {
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.speed-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
.demo-content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.process-list {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.process-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1.5fr 1fr;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1.5fr 1fr;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
.speed-control button {
|
||||
background: transparent;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
padding: 0.3rem 0.8rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.process-item:hover {
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.process-item.selected {
|
||||
.speed-control button.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.state-badge {
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.state-badge.running {
|
||||
background: var(--vp-c-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.state-badge.waiting {
|
||||
background: var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.process-detail {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
color: var(--vp-c-brand-1);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.detail-item .value {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.memory-layout {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.layout-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.layout-visual {
|
||||
.cpu-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
height: 120px;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.segment {
|
||||
.cpu-core {
|
||||
width: 240px;
|
||||
height: 90px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
.cpu-core.active {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
box-shadow: 0 0 20px var(--vp-c-brand-soft);
|
||||
}
|
||||
.cpu-title {
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.current-task {
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.task-badge {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
padding: 0.2rem 0.8rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.task-badge.idle {
|
||||
background: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.seg-name {
|
||||
font-size: 0.7rem;
|
||||
/* 连接线动画占位,简化效果,用发亮的虚线替代 */
|
||||
.connector {
|
||||
width: 2px;
|
||||
height: 30px;
|
||||
background: var(--vp-c-divider);
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.processes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.processes-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.process-card {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.process-card.active {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.process-card.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 4px;
|
||||
background: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.p-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.p-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
.status-badge.waiting {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.status-badge.running {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: var(--vp-c-success-1);
|
||||
}
|
||||
|
||||
.p-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--vp-c-brand-1);
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
.process-card.active .progress-fill {
|
||||
background: var(--vp-c-success-1);
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.explanation-box {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
border-left: 4px solid var(--vp-c-success-1);
|
||||
border-radius: 0 8px 8px 0;
|
||||
font-size: 0.95rem;
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
.explanation-box.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,60 +1,38 @@
|
||||
<template>
|
||||
<div class="register-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">寄存器:能「记住」一个 0 或 1 的小单元</span>
|
||||
<span class="subtitle">只有点「写入」时才会把当前输入记下来,平时改输入不会影响已存的值</span>
|
||||
</div>
|
||||
<div class="demo-label">1 位寄存器 ── 只在"写入"时更新存储值</div>
|
||||
|
||||
<div class="why-what-box">
|
||||
<p class="why-p">
|
||||
<strong>为啥要看这个?</strong>CPU 算到一半要暂时「记住」中间结果,寄存器就是干这个的。它和「直接连线」不同:改输入不会立刻改变里面存的东西,必须主动点一次「写入」才会更新。
|
||||
</p>
|
||||
<p class="what-p">
|
||||
<strong>这些词是啥?</strong>
|
||||
<span class="term">输入</span>:你想写进去的 0 或 1。
|
||||
<span class="term">写入</span>:点一下,把当前输入「锁进」寄存器。
|
||||
<span class="term">存储值</span>:寄存器里现在记着的数(只有写入时才会变)。
|
||||
<span class="term">输出</span>:从寄存器读出来的数,和存储值一样。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
<label class="ctrl-group">
|
||||
<span class="ctrl-label">输入</span>
|
||||
<div class="reg-panel">
|
||||
<!-- Input -->
|
||||
<div class="reg-block">
|
||||
<span class="reg-title">输入</span>
|
||||
<button
|
||||
class="input-toggle"
|
||||
class="toggle-btn"
|
||||
:class="{ on: inputData === 1 }"
|
||||
@click="inputData = inputData === 1 ? 0 : 1"
|
||||
>
|
||||
{{ inputData }}
|
||||
</button>
|
||||
</label>
|
||||
<button class="write-btn" :class="{ flash: isWriting }" @click="writeOnce">
|
||||
写入
|
||||
</button>
|
||||
<label class="ctrl-group">
|
||||
<span class="ctrl-label">存储</span>
|
||||
<span class="stored-val" :class="{ on: storedData === 1 }">{{ storedData }}</span>
|
||||
</label>
|
||||
<span class="ctrl-group">
|
||||
<span class="ctrl-label">输出</span>
|
||||
<span class="output-val" :class="{ on: storedData === 1 }">{{ storedData }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="visualization-area">
|
||||
<div class="flow-strip">
|
||||
<span class="flow-item">输入 {{ inputData }}</span>
|
||||
<span class="flow-arrow" :class="{ active: isWriting }">{{ isWriting ? '写入中 →' : '— 点「写入」才更新 →' }}</span>
|
||||
<span class="flow-item flow-store" :class="{ flash: isWriting }">存 {{ storedData }}</span>
|
||||
>{{ inputData }}</button>
|
||||
</div>
|
||||
|
||||
<!-- Write -->
|
||||
<button class="write-btn" :class="{ flash: isWriting }" @click="writeOnce">
|
||||
写入 →
|
||||
</button>
|
||||
|
||||
<!-- Stored -->
|
||||
<div class="reg-block">
|
||||
<span class="reg-title">存储</span>
|
||||
<span class="val-box" :class="{ on: storedData === 1, flash: isWriting }">{{ storedData }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Output -->
|
||||
<div class="reg-block">
|
||||
<span class="reg-title">输出</span>
|
||||
<span class="val-box out" :class="{ on: storedData === 1 }">{{ storedData }}</span>
|
||||
</div>
|
||||
<p class="flow-hint">
|
||||
{{ inputData !== storedData ? '输入和存储不一样:点「写入」会把当前输入记进去。' : '输入和存储已一致。' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>寄存器只在「写入」那一刻更新,其余时间一直保持原来的值,所以 CPU 能稳定保存中间结果。
|
||||
<div class="status-line">
|
||||
{{ inputData !== storedData ? '⚡ 输入≠存储 → 点"写入"即可更新' : '✓ 输入与存储一致' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -69,9 +47,7 @@ const isWriting = ref(false)
|
||||
const writeOnce = () => {
|
||||
isWriting.value = true
|
||||
storedData.value = inputData.value
|
||||
window.setTimeout(() => {
|
||||
isWriting.value = false
|
||||
}, 400)
|
||||
setTimeout(() => { isWriting.value = false }, 400)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,75 +56,44 @@ const writeOnce = () => {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
display: block;
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
display: block;
|
||||
font-size: 0.82rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.why-what-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.65rem 0.85rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.why-what-box .why-p {
|
||||
margin: 0 0 0.4rem;
|
||||
}
|
||||
|
||||
.why-what-box .what-p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.why-what-box .term {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
/* ── panel ── */
|
||||
.reg-panel {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.5rem 0.7rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.ctrl-group {
|
||||
display: inline-flex;
|
||||
.reg-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.ctrl-label {
|
||||
font-size: 0.82rem;
|
||||
color: var(--vp-c-text-2);
|
||||
.reg-title {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.input-toggle {
|
||||
.toggle-btn {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 6px;
|
||||
@@ -156,26 +101,28 @@ const writeOnce = () => {
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.input-toggle.on {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
.toggle-btn.on {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.write-btn {
|
||||
padding: 0.35rem 0.8rem;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border-radius: 6px;
|
||||
border: 2px solid var(--vp-c-warning-1, #d97706);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-warning-1, #d97706);
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.write-btn:hover {
|
||||
@@ -187,93 +134,44 @@ const writeOnce = () => {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stored-val,
|
||||
.output-val {
|
||||
.val-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.4rem;
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 6px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stored-val.on,
|
||||
.output-val.on {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
.val-box.on {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.visualization-area {
|
||||
margin-bottom: 0.75rem;
|
||||
.val-box.flash {
|
||||
box-shadow: 0 0 0 3px var(--vp-c-warning-soft, rgba(217, 119, 6, 0.25));
|
||||
}
|
||||
|
||||
.flow-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 0.9rem;
|
||||
.val-box.out {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.flow-item {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flow-store {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.flow-store.flash {
|
||||
box-shadow: 0 0 0 2px var(--vp-c-warning-1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
.status-line {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.flow-arrow.active {
|
||||
color: var(--vp-c-warning-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flow-hint {
|
||||
margin: 0.4rem 0 0;
|
||||
font-size: 0.82rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.control-panel {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@media (max-width: 480px) {
|
||||
.reg-panel {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="sand-demo">
|
||||
<div class="demo-label">从沙子到智能 ── 每一层都是对下一层的封装</div>
|
||||
|
||||
<div class="layers">
|
||||
<div
|
||||
v-for="(layer, i) in layers" :key="i"
|
||||
class="layer-row"
|
||||
:class="{ active: activeLayer === i }"
|
||||
@mouseenter="activeLayer = i"
|
||||
@mouseleave="activeLayer = null"
|
||||
>
|
||||
<div class="layer-num">{{ i + 1 }}</div>
|
||||
<div class="layer-icon">{{ layer.icon }}</div>
|
||||
<div class="layer-body">
|
||||
<div class="layer-name">{{ layer.name }}</div>
|
||||
<div class="layer-desc">{{ layer.desc }}</div>
|
||||
</div>
|
||||
<div class="layer-scale">{{ layer.scale }}</div>
|
||||
<div v-if="i < layers.length - 1" class="arrow-down">
|
||||
<span class="arrow-label">{{ layer.arrow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-caption">层层抽象封装,最底层的物理材料最终变成通用计算平台</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeLayer = ref(null)
|
||||
|
||||
const layers = [
|
||||
{ icon: '🏖️', name: '沙子(硅)', desc: '地球上最丰富的元素之一,提炼出高纯度硅', scale: '原材料', arrow: '提纯 → 切割' },
|
||||
{ icon: '💿', name: '硅晶圆', desc: '直径约 30cm 的单晶硅片,表面极其光滑', scale: '基底', arrow: '光刻 → 蚀刻 → 掺杂' },
|
||||
{ icon: '🔌', name: '晶体管(开关)', desc: 'Gate=1 导通,Gate=0 断开,用电压控制电流', scale: '数百亿 / 芯片', arrow: '组合成逻辑电路' },
|
||||
{ icon: '🔲', name: '逻辑门', desc: 'AND / OR / NOT / XOR,实现基本布尔运算', scale: '数十亿', arrow: '组合成功能模块' },
|
||||
{ icon: '⚙️', name: '功能单元', desc: '加法器、寄存器、多路选择器……各司其职', scale: '数百个', arrow: '集成为处理器' },
|
||||
{ icon: '🧠', name: 'CPU 核心', desc: 'ALU + 控制器 + 寄存器组,取指→解码→执行→写回', scale: '1–128 核', arrow: '软件编程' },
|
||||
{ icon: '🚀', name: '软件应用', desc: '操作系统 / AI / 游戏 / 网页……一切皆指令', scale: '无限可能', arrow: '' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sand-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.layers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.layer-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.55rem 0.7rem;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
transition: all 0.15s;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.layer-row:hover,
|
||||
.layer-row.active {
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.layer-num {
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.65rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.layer-row.active .layer-num {
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.layer-icon {
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.layer-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.layer-name {
|
||||
font-size: 0.88rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.layer-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.layer-scale {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-3);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.15rem 0.45rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* ── arrow between layers ── */
|
||||
.arrow-down {
|
||||
position: absolute;
|
||||
left: 1.1rem;
|
||||
bottom: -0.55rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.arrow-label {
|
||||
font-size: 0.6rem;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 0 0.3rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.demo-caption {
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 0.6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.layer-scale {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+148
-131
@@ -1,47 +1,45 @@
|
||||
<template>
|
||||
<div class="transistor-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">晶体管:数字世界的开关</span>
|
||||
<span class="subtitle">Gate 电压决定电流能否通过</span>
|
||||
</div>
|
||||
<div class="demo-label">MOSFET 晶体管示意 ── 点击切换 Gate 电压</div>
|
||||
|
||||
<div class="states">
|
||||
<div class="state-card">
|
||||
<div class="state-label">Gate = 0(低电压)</div>
|
||||
<div class="channel-row">
|
||||
<span class="terminal">源极</span>
|
||||
<div class="channel-track off">
|
||||
<span class="block-icon">✕ 断开</span>
|
||||
</div>
|
||||
<span class="terminal">漏极</span>
|
||||
</div>
|
||||
<div class="output-line">输出:<strong>0</strong></div>
|
||||
<div class="schematic" @click="gateOn = !gateOn">
|
||||
<!-- Source terminal -->
|
||||
<div class="terminal-box source">
|
||||
<span class="pin-label">源极<br><span class="en">Source</span></span>
|
||||
<div class="pin-wire" :class="{ active: gateOn }"></div>
|
||||
</div>
|
||||
|
||||
<div class="state-card">
|
||||
<div class="state-label">Gate = 1(高电压)</div>
|
||||
<div class="channel-row">
|
||||
<span class="terminal">源极</span>
|
||||
<div class="channel-track on">
|
||||
<span class="flow-dot d1" />
|
||||
<span class="flow-dot d2" />
|
||||
<span class="flow-dot d3" />
|
||||
<span class="flow-label">导通</span>
|
||||
</div>
|
||||
<span class="terminal">漏极</span>
|
||||
<!-- Channel -->
|
||||
<div class="channel-area">
|
||||
<div class="gate-indicator" :class="{ on: gateOn }">
|
||||
<span class="gate-label">Gate</span>
|
||||
<span class="gate-val">{{ gateOn ? '1' : '0' }}</span>
|
||||
</div>
|
||||
<div class="output-line">输出:<strong>1</strong></div>
|
||||
<div class="channel-bar" :class="{ conducting: gateOn }">
|
||||
<template v-if="gateOn">
|
||||
<span class="electron e1"></span>
|
||||
<span class="electron e2"></span>
|
||||
<span class="electron e3"></span>
|
||||
</template>
|
||||
<span v-else class="block-mark">✕</span>
|
||||
</div>
|
||||
<div class="channel-status">{{ gateOn ? '导通 → 输出 1' : '断开 → 输出 0' }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Drain terminal -->
|
||||
<div class="terminal-box drain">
|
||||
<div class="pin-wire" :class="{ active: gateOn }"></div>
|
||||
<span class="pin-label">漏极<br><span class="en">Drain</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>晶体管是“电控开关”:Gate=1 导通、Gate=0 断开,所有数字计算都建立在这种 0/1 开关之上。
|
||||
</div>
|
||||
<div class="tap-hint">👆 点击切换 Gate 电压</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 纯静态展示,无需交互
|
||||
import { ref } from 'vue'
|
||||
const gateOn = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -49,149 +47,168 @@
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
padding: 1rem 1.2rem;
|
||||
margin: 1rem 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
.demo-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.states {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.state-card {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.state-label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.channel-row {
|
||||
/* ── layout ── */
|
||||
.schematic {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
font-size: 0.82rem;
|
||||
/* ── terminals ── */
|
||||
.terminal-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.pin-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
flex-shrink: 0;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.channel-track {
|
||||
flex: 1;
|
||||
min-height: 2.5rem;
|
||||
.pin-label .en {
|
||||
font-size: 0.65rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.pin-wire {
|
||||
width: 2.5rem;
|
||||
height: 3px;
|
||||
background: var(--vp-c-divider);
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.pin-wire.active {
|
||||
background: var(--vp-c-brand-1);
|
||||
box-shadow: 0 0 6px var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
/* ── channel ── */
|
||||
.channel-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
min-width: 7rem;
|
||||
}
|
||||
|
||||
.gate-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.25rem 0.65rem;
|
||||
border-radius: 6px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.gate-indicator.on {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.gate-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.gate-val {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.gate-indicator.on .gate-val {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.channel-bar {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.channel-track.off {
|
||||
background: var(--vp-c-bg-alt);
|
||||
.channel-bar.conducting {
|
||||
background: var(--vp-c-success-soft, rgba(22, 163, 74, 0.12));
|
||||
border-color: var(--vp-c-success, #16a34a);
|
||||
}
|
||||
|
||||
.channel-track.on {
|
||||
background: var(--vp-c-success-soft);
|
||||
border-color: var(--vp-c-success);
|
||||
}
|
||||
|
||||
.block-icon {
|
||||
.block-mark {
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.flow-dot {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
.electron {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-success);
|
||||
background: var(--vp-c-success, #16a34a);
|
||||
position: absolute;
|
||||
left: -10%;
|
||||
animation: flow 1.5s linear infinite;
|
||||
animation: flow 1.2s linear infinite;
|
||||
}
|
||||
|
||||
.flow-dot.d2 {
|
||||
animation-delay: 0.45s;
|
||||
}
|
||||
|
||||
.flow-dot.d3 {
|
||||
animation-delay: 0.9s;
|
||||
}
|
||||
|
||||
.flow-label {
|
||||
margin-left: 0.4rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-success-1);
|
||||
font-weight: 600;
|
||||
}
|
||||
.electron.e2 { animation-delay: 0.4s; }
|
||||
.electron.e3 { animation-delay: 0.8s; }
|
||||
|
||||
@keyframes flow {
|
||||
from {
|
||||
left: -10%;
|
||||
}
|
||||
to {
|
||||
left: 105%;
|
||||
}
|
||||
0% { left: -8%; opacity: 0; }
|
||||
10% { opacity: 1; }
|
||||
90% { opacity: 1; }
|
||||
100% { left: 108%; opacity: 0; }
|
||||
}
|
||||
|
||||
.output-line {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.output-line strong {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
.channel-status {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
.channel-bar.conducting + .channel-status {
|
||||
color: var(--vp-c-success, #16a34a);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.states {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.tap-hint {
|
||||
text-align: center;
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.pin-wire { width: 1.5rem; }
|
||||
.channel-area { min-width: 5rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,13 +6,19 @@
|
||||
<div class="mode-buttons">
|
||||
<button
|
||||
:class="['mode-btn', { active: mode === 'serial' }]"
|
||||
@click="mode = 'serial'; reset()"
|
||||
@click="
|
||||
mode = 'serial';
|
||||
reset()
|
||||
"
|
||||
>
|
||||
串行传输(现代)
|
||||
</button>
|
||||
<button
|
||||
:class="['mode-btn', { active: mode === 'parallel' }]"
|
||||
@click="mode = 'parallel'; reset()"
|
||||
@click="
|
||||
mode = 'parallel';
|
||||
reset()
|
||||
"
|
||||
>
|
||||
并行传输(旧时代)
|
||||
</button>
|
||||
@@ -31,7 +37,7 @@
|
||||
:key="i"
|
||||
class="bit"
|
||||
:class="{ sent: sentBits.includes(i) }"
|
||||
>{{ bit }}</span>
|
||||
>{{ bit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +51,7 @@
|
||||
:key="'p' + i"
|
||||
class="particle"
|
||||
:style="{ left: p.progress + '%', top: '50%' }"
|
||||
>{{ p.bit }}</span>
|
||||
>{{ p.bit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mode === 'parallel'" class="wire-group parallel-group">
|
||||
@@ -55,7 +61,7 @@
|
||||
v-if="parallelParticle && parallelParticle.lane === l - 1"
|
||||
class="particle"
|
||||
:style="{ left: parallelParticle.progress + '%', top: '50%' }"
|
||||
>{{ parallelBits[l - 1] || '·' }}</span>
|
||||
>{{ parallelBits[l - 1] || '·' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,9 +75,13 @@
|
||||
v-for="(bit, i) in receivedBits"
|
||||
:key="'r' + i"
|
||||
class="bit received"
|
||||
>{{ bit }}</span>
|
||||
>{{ bit }}</span>
|
||||
</div>
|
||||
<div v-if="checksumResult !== null" class="checksum-badge" :class="checksumResult ? 'ok' : 'fail'">
|
||||
<div
|
||||
v-if="checksumResult !== null"
|
||||
class="checksum-badge"
|
||||
:class="checksumResult ? 'ok' : 'fail'"
|
||||
>
|
||||
{{ checksumResult ? '✓ 校验通过' : '✕ 校验失败' }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +95,9 @@
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="s-label">传输速率</span>
|
||||
<span class="s-val">{{ mode === 'serial' ? '1 位/次' : '8 位/次' }}</span>
|
||||
<span class="s-val">{{
|
||||
mode === 'serial' ? '1 位/次' : '8 位/次'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="s-label">状态</span>
|
||||
@@ -99,8 +111,10 @@
|
||||
</button>
|
||||
|
||||
<div class="note-box">
|
||||
<strong>提示:等等,串行不是更慢吗?</strong><br>
|
||||
表面上是的——但现代串行接口(USB 4、PCIe)传输频率高达每秒 <strong>数百亿次</strong>,而并行线路之间会产生 <em>信号串扰(Crosstalk)</em>,反而限制了速度。所以高速接口全面转向了串行。
|
||||
<strong>提示:等等,串行不是更慢吗?</strong><br />
|
||||
表面上是的——但现代串行接口(USB 4、PCIe)传输频率高达每秒
|
||||
<strong>数百亿次</strong>,而并行线路之间会产生
|
||||
<em>信号串扰(Crosstalk)</em>,反而限制了速度。所以高速接口全面转向了串行。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -109,7 +123,7 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const mode = ref('serial')
|
||||
const dataBits = ref([1,0,1,1,0,0,1,0]) // "Hello" first byte 0b10110010
|
||||
const dataBits = ref([1, 0, 1, 1, 0, 0, 1, 0]) // "Hello" first byte 0b10110010
|
||||
const sentBits = ref([])
|
||||
const receivedBits = ref([])
|
||||
const particles = ref([])
|
||||
@@ -141,7 +155,9 @@ const statusColor = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }
|
||||
function sleep(ms) {
|
||||
return new Promise((r) => setTimeout(r, ms))
|
||||
}
|
||||
|
||||
async function send() {
|
||||
if (isSending.value) return
|
||||
@@ -156,7 +172,7 @@ async function send() {
|
||||
|
||||
// Checksum simulation
|
||||
await sleep(400)
|
||||
checksumResult.value = true // always pass in demo
|
||||
checksumResult.value = true // always pass in demo
|
||||
isSending.value = false
|
||||
}
|
||||
|
||||
@@ -171,7 +187,7 @@ async function sendSerial() {
|
||||
p.progress = prog
|
||||
await sleep(35)
|
||||
}
|
||||
particles.value = particles.value.filter(x => x !== p)
|
||||
particles.value = particles.value.filter((x) => x !== p)
|
||||
receivedBits.value.push(bit)
|
||||
await sleep(30)
|
||||
}
|
||||
@@ -181,7 +197,10 @@ async function sendParallel() {
|
||||
sentBits.value = dataBits.value.map((_, i) => i)
|
||||
parallelBits.value = [...dataBits.value]
|
||||
for (let prog = 0; prog <= 100; prog += 8) {
|
||||
parallelParticle.value = { progress: prog, lane: Math.floor(Math.random() * 8) }
|
||||
parallelParticle.value = {
|
||||
progress: prog,
|
||||
lane: Math.floor(Math.random() * 8)
|
||||
}
|
||||
await sleep(40)
|
||||
}
|
||||
parallelParticle.value = null
|
||||
@@ -249,10 +268,17 @@ async function sendParallel() {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.device-icon { font-size: 2rem; }
|
||||
.device-label { font-size: 0.8rem; font-weight: bold; color: var(--vp-c-text-2); }
|
||||
.device-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.device-label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.data-bits, .received-bits {
|
||||
.data-bits,
|
||||
.received-bits {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
@@ -273,8 +299,15 @@ async function sendParallel() {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bit.sent { background: var(--vp-c-brand-soft); border-color: var(--vp-c-brand); }
|
||||
.bit.received { background: #d1fae5; border-color: #059669; color: #065f46; }
|
||||
.bit.sent {
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
.bit.received {
|
||||
background: #d1fae5;
|
||||
border-color: #059669;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.checksum-badge {
|
||||
margin-top: 4px;
|
||||
@@ -283,8 +316,14 @@ async function sendParallel() {
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.checksum-badge.ok { background: #d1fae5; color: #065f46; }
|
||||
.checksum-badge.fail { background: #fee2e2; color: #991b1b; }
|
||||
.checksum-badge.ok {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
.checksum-badge.fail {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
/* Wires */
|
||||
.wire-container {
|
||||
@@ -311,8 +350,14 @@ async function sendParallel() {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wire-group.serial .wire { height: 20px; }
|
||||
.parallel-group { display: flex; flex-direction: column; gap: 2px; }
|
||||
.wire-group.serial .wire {
|
||||
height: 20px;
|
||||
}
|
||||
.parallel-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.particle {
|
||||
position: absolute;
|
||||
@@ -337,11 +382,25 @@ async function sendParallel() {
|
||||
padding: 0.6rem 0.85rem;
|
||||
}
|
||||
|
||||
.status-item { display: flex; flex-direction: column; gap: 2px; }
|
||||
.s-label { font-size: 0.72rem; color: var(--vp-c-text-3); }
|
||||
.s-val { font-size: 0.88rem; font-weight: bold; }
|
||||
.s-val.green { color: #059669; }
|
||||
.s-val.yellow { color: #d97706; }
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.s-label {
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
.s-val {
|
||||
font-size: 0.88rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.s-val.green {
|
||||
color: #059669;
|
||||
}
|
||||
.s-val.yellow {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
padding: 0.5rem 1.2rem;
|
||||
@@ -356,7 +415,10 @@ async function sendParallel() {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.send-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.send-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.note-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
|
||||
@@ -35,8 +35,10 @@ const scopes = [
|
||||
const updateExplanation = () => {
|
||||
const texts = {
|
||||
global: '在全局作用域,只能使用全局变量 appName',
|
||||
function: '在函数作用域,可以使用自己的 message 和全局的 appName(作用域链查找)',
|
||||
block: '在块级作用域,可以使用自己的 greeting,以及外层的 message 和 appName'
|
||||
function:
|
||||
'在函数作用域,可以使用自己的 message 和全局的 appName(作用域链查找)',
|
||||
block:
|
||||
'在块级作用域,可以使用自己的 greeting,以及外层的 message 和 appName'
|
||||
}
|
||||
explanation.value = texts[activeScope.value]
|
||||
}
|
||||
@@ -68,13 +70,13 @@ updateExplanation()
|
||||
v-for="scope in scopes"
|
||||
:key="scope.id"
|
||||
class="level"
|
||||
:class="{ active: activeScope === scope.id, dimmed: activeScope !== scope.id }"
|
||||
:class="{
|
||||
active: activeScope === scope.id,
|
||||
dimmed: activeScope !== scope.id
|
||||
}"
|
||||
:style="{ borderLeftColor: scope.color }"
|
||||
>
|
||||
<div
|
||||
class="level-header"
|
||||
:style="{ color: scope.color }"
|
||||
>
|
||||
<div class="level-header" :style="{ color: scope.color }">
|
||||
{{ scope.name }}
|
||||
</div>
|
||||
<div class="level-vars">
|
||||
@@ -86,10 +88,7 @@ updateExplanation()
|
||||
>
|
||||
<span class="var-name">{{ v.name }}</span>
|
||||
<span class="var-value">= {{ v.value }}</span>
|
||||
<span
|
||||
v-if="!v.own"
|
||||
class="var-from"
|
||||
>← {{ v.from }}</span>
|
||||
<span v-if="!v.own" class="var-from">← {{ v.from }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,9 +96,7 @@ updateExplanation()
|
||||
|
||||
<!-- 说明 -->
|
||||
<div class="explanation-box">
|
||||
<div class="explanation-title">
|
||||
💡 当前位置可见的变量
|
||||
</div>
|
||||
<div class="explanation-title">💡 当前位置可见的变量</div>
|
||||
<div class="explanation-text">
|
||||
{{ explanation }}
|
||||
</div>
|
||||
|
||||
@@ -86,14 +86,18 @@ const bestPractices = ref([
|
||||
const codeComparisons = ref([
|
||||
{
|
||||
scenario: '函数返回值',
|
||||
withInference: 'function add(a: number, b: number) {\n return a + b // 推断为 number\n}',
|
||||
withAnnotation: 'function add(a: number, b: number): number {\n return a + b\n}',
|
||||
withInference:
|
||||
'function add(a: number, b: number) {\n return a + b // 推断为 number\n}',
|
||||
withAnnotation:
|
||||
'function add(a: number, b: number): number {\n return a + b\n}',
|
||||
recommendation: '推荐使用推断'
|
||||
},
|
||||
{
|
||||
scenario: '复杂对象',
|
||||
withInference: 'const user = {\n name: "张三",\n age: 25,\n email: "test@example.com"\n} // 类型自动推断',
|
||||
withAnnotation: 'interface User {\n name: string\n age: number\n email: string\n}\n\nconst user: User = { ... }',
|
||||
withInference:
|
||||
'const user = {\n name: "张三",\n age: 25,\n email: "test@example.com"\n} // 类型自动推断',
|
||||
withAnnotation:
|
||||
'interface User {\n name: string\n age: number\n email: string\n}\n\nconst user: User = { ... }',
|
||||
recommendation: '复杂结构建议用接口'
|
||||
}
|
||||
])
|
||||
@@ -107,12 +111,13 @@ const codeComparisons = ref([
|
||||
<!-- 概念说明 -->
|
||||
<div class="concept-section">
|
||||
<div class="concept-card">
|
||||
<div class="concept-icon">
|
||||
🧠
|
||||
</div>
|
||||
<div class="concept-icon">🧠</div>
|
||||
<div class="concept-content">
|
||||
<h4>什么是类型推断?</h4>
|
||||
<p>TypeScript 很聪明,它能根据你写的代码自动推断出变量的类型,不需要每次都手动标注。</p>
|
||||
<p>
|
||||
TypeScript
|
||||
很聪明,它能根据你写的代码自动推断出变量的类型,不需要每次都手动标注。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,15 +129,16 @@ const codeComparisons = ref([
|
||||
<div
|
||||
v-for="example in codeExamples"
|
||||
:key="example.id"
|
||||
:class="['example-card', { active: currentExample.id === example.id }]"
|
||||
:class="[
|
||||
'example-card',
|
||||
{ active: currentExample.id === example.id }
|
||||
]"
|
||||
@click="selectExample(example)"
|
||||
>
|
||||
<div class="example-code">
|
||||
{{ example.code }}
|
||||
</div>
|
||||
<div class="example-type">
|
||||
→ {{ example.inferredType }}
|
||||
</div>
|
||||
<div class="example-type">→ {{ example.inferredType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,9 +154,7 @@ const codeComparisons = ref([
|
||||
<pre><code class="typescript">{{ currentExample.code }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="inference-arrow">
|
||||
→
|
||||
</div>
|
||||
<div class="inference-arrow">→</div>
|
||||
|
||||
<div class="type-panel">
|
||||
<div class="panel-header">
|
||||
@@ -164,9 +168,7 @@ const codeComparisons = ref([
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<div class="explanation-icon">
|
||||
💡
|
||||
</div>
|
||||
<div class="explanation-icon">💡</div>
|
||||
<div class="explanation-text">
|
||||
{{ currentExample.explanation }}
|
||||
</div>
|
||||
@@ -183,16 +185,8 @@ const codeComparisons = ref([
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="controls">
|
||||
<button
|
||||
class="btn-danger"
|
||||
@click="tryTypeError"
|
||||
>
|
||||
尝试类型错误
|
||||
</button>
|
||||
<button
|
||||
class="btn-secondary"
|
||||
@click="showError = false; errorMessage = ''"
|
||||
>
|
||||
<button class="btn-danger" @click="tryTypeError">尝试类型错误</button>
|
||||
<button class="btn-secondary" @click="showError = false; errorMessage = ''">
|
||||
清除消息
|
||||
</button>
|
||||
</div>
|
||||
@@ -210,10 +204,7 @@ const codeComparisons = ref([
|
||||
{{ practice.title }}
|
||||
</div>
|
||||
<ul class="practice-list">
|
||||
<li
|
||||
v-for="(item, i) in practice.items"
|
||||
:key="i"
|
||||
>
|
||||
<li v-for="(item, i) in practice.items" :key="i">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -234,15 +225,11 @@ const codeComparisons = ref([
|
||||
</div>
|
||||
<div class="comparison-codes">
|
||||
<div class="comparison-code">
|
||||
<div class="code-label">
|
||||
使用推断
|
||||
</div>
|
||||
<div class="code-label">使用推断</div>
|
||||
<pre><code class="typescript">{{ comparison.withInference }}</code></pre>
|
||||
</div>
|
||||
<div class="comparison-code">
|
||||
<div class="code-label">
|
||||
显式注解
|
||||
</div>
|
||||
<div class="code-label">显式注解</div>
|
||||
<pre><code class="typescript">{{ comparison.withAnnotation }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,7 +252,8 @@ const codeComparisons = ref([
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
@@ -380,7 +368,8 @@ h4 {
|
||||
}
|
||||
}
|
||||
|
||||
.code-panel, .type-panel {
|
||||
.code-panel,
|
||||
.type-panel {
|
||||
flex: 1;
|
||||
border: 2px solid var(--vp-c-border);
|
||||
border-radius: 8px;
|
||||
@@ -398,7 +387,8 @@ h4 {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.code-icon, .type-icon {
|
||||
.code-icon,
|
||||
.type-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -559,6 +559,10 @@ import RestfulUrlDemo from './components/appendix/api-design/RestfulUrlDemo.vue'
|
||||
import StatusCodeDemo from './components/appendix/api-design/StatusCodeDemo.vue'
|
||||
import ErrorHandlingDemo from './components/appendix/api-design/ErrorHandlingDemo.vue'
|
||||
import ApiVersioningDemo from './components/appendix/api-design/ApiVersioningDemo.vue'
|
||||
import ApiStyleCompare from './components/appendix/api-design/ApiStyleCompare.vue'
|
||||
import ResponseStructureDemo from './components/appendix/api-design/ResponseStructureDemo.vue'
|
||||
import DataFieldDesignDemo from './components/appendix/api-design/DataFieldDesignDemo.vue'
|
||||
import ErrorResponseDesignDemo from './components/appendix/api-design/ErrorResponseDesignDemo.vue'
|
||||
|
||||
// JavaScript Intro Components
|
||||
import VariableBoxDemo from './components/appendix/javascript-intro/VariableBoxDemo.vue'
|
||||
@@ -1172,6 +1176,10 @@ export default {
|
||||
app.component('StatusCodeDemo', StatusCodeDemo)
|
||||
app.component('ErrorHandlingDemo', ErrorHandlingDemo)
|
||||
app.component('ApiVersioningDemo', ApiVersioningDemo)
|
||||
app.component('ApiStyleCompare', ApiStyleCompare)
|
||||
app.component('ResponseStructureDemo', ResponseStructureDemo)
|
||||
app.component('DataFieldDesignDemo', DataFieldDesignDemo)
|
||||
app.component('ErrorResponseDesignDemo', ErrorResponseDesignDemo)
|
||||
|
||||
// Database Intro Extra Components Registration
|
||||
app.component('DatabaseEvolutionDemo', DatabaseEvolutionDemo)
|
||||
|
||||
@@ -1,625 +1,84 @@
|
||||
# 网络:两台电脑如何对话
|
||||
# 计算机网络:从输入网址到返回结果的过程
|
||||
|
||||
::: tip 🎯 核心问题
|
||||
**当你在浏览器输入 www.baidu.com 并按下回车,到底发生了什么?** 这个简单动作背后,其实隐藏着一个庞大的"快递系统":从填写订单(URL)到查询地址簿(DNS),从建立运输通道(TCP)到快递员送货(HTTP),最终在你屏幕上展示(渲染)。本章带你完整理解这个神奇的过程。
|
||||
**当你在浏览器输入 www.google.com 并按下回车,到底发生了什么?**
|
||||
|
||||
这个看似简单的动作,背后隐藏着一个庞大精密的跨国“快递系统”。从填写订单(URL解析)到查询地址簿(DNS解析),从建立运输通道(TCP握手)到快递员送货(HTTP请求与响应),最终在你屏幕上拆开包裹组装(浏览器渲染)。本章带你零基础、完整理解这个神奇的过程。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 0. 五层模型总览:快递公司的组织架构
|
||||
## 全景演示:网络世界的快递系统
|
||||
|
||||
现代计算机网络就像一个**快递公司**,采用五层分层模型,每层负责不同的工作:
|
||||
你可以通过下方的交互组件,直观地体验从输入网址到看到网页的 5 个关键步骤。先自己点一点,然后再看底下的详细解释!
|
||||
|
||||
<CFNetworkLayers />
|
||||
|
||||
::: tip 💡 为什么需要分层?
|
||||
想象一个没有分工的快递公司:
|
||||
|
||||
- **每个人什么都干**:接电话、分拣、打包、开车送货...
|
||||
- **效率极低**:没人专精,什么都做不好
|
||||
- **难以扩展**:想加个"航空运输",所有员工都要重新培训
|
||||
|
||||
**分层设计**解决了这些问题:
|
||||
|
||||
- **模块化**:每层独立设计和实现,改一层不影响其他层
|
||||
- **易维护**:网络慢了?查物理层和数据链路层;安全问题?查应用层
|
||||
- **标准化**:统一的接口和协议,不同厂商的设备能互相通信
|
||||
- **可扩展**:新技术可以替换某一层,比如从铜线换成光纤,只需改物理层
|
||||
:::
|
||||
|
||||
| 层级 | 技术名称 | 快递公司类比 | 核心职责 | 常见协议/设备 |
|
||||
| ----- | ---------- | ---------------- | ---------------------------------------- | ------------------ |
|
||||
| **5** | 应用层 | **客户服务部门** | 处理具体业务(网页、邮件、文件传输) | HTTP, FTP, SMTP |
|
||||
| **4** | 传输层 | **包裹分拣组** | 确保包裹可靠送达(_TCP_)或快速送达(_UDP_) | TCP, UDP |
|
||||
| **3** | 网络层 | **路由规划部** | 规划最佳运输路线,选择走哪条路 | IP, 路由器 |
|
||||
| **2** | 数据链路层 | **车队管理** | 管理车辆之间的通信,MAC 地址寻址 | 以太网, 交换机 |
|
||||
| **1** | 物理层 | **道路和车辆** | 实际的物理传输(电缆、光纤、无线电波) | 网线, 光纤, 无线电 |
|
||||
|
||||
::: tip 📊 逐行解读这张表
|
||||
**第5层(应用层)**:这是你直接接触的层。浏览器打开网页、邮件客户端收发邮件,都是在调用这一层的服务。它负责处理"具体的业务逻辑"。
|
||||
|
||||
**第4层(传输层)**:应用层把数据给它,它负责决定用什么方式"寄送"。TCP 像挂号信(可靠但慢),UDP 像平信(快但可能丢)。这一层用**端口号**区分不同的应用程序。
|
||||
|
||||
**第3层(网络层)**:这是"全球定位系统"层。IP 地址就在这一层,路由器根据 IP 地址规划路线:"从北京到上海,应该走哪条高速公路?"
|
||||
|
||||
**第2层(数据链路层)**:这一层负责"两站之间"的运输。就像快递车从北京分拣中心开到天津分拣中心,这一段路的通信规则由数据链路层规定。MAC 地址(设备身份证)也在这一层。
|
||||
|
||||
**第1层(物理层)**:这是最底层,实实在在的物理介质。网线里的电信号、光纤里的光信号、Wi-Fi 的无线电波,都是物理层负责的。
|
||||
:::
|
||||
<UrlToBrowserDemo />
|
||||
|
||||
---
|
||||
|
||||
## 1. 物理层:道路和车辆
|
||||
## 1. 填写购物单 (URL 解析)
|
||||
|
||||
### 1.1 基本概念
|
||||
当你在浏览器的地址栏中输入 `https://www.google.com` 这样一段地址并按下回车,这就像是你准备去商店买东西,首先要在**购物单**上写清楚:
|
||||
|
||||
::: tip 💡 物理层是什么?
|
||||
物理层负责在物理介质上传输原始的比特流(0 和 1)。
|
||||
- **交通方式 (Protocol)**:例如 `https://`,代表你想坐安全级别的最高的“运钞车”(加密通信)去。如果是单纯的 `http://`,就相当于坐普通的“大巴”(明文传输),路上可能会被人偷看行李。
|
||||
- **店铺地址 (Host)**:例如 `www.google.com`,也就是你要去哪家店(域名)。
|
||||
- **商品位置 (Path)**:例如 `/search`,意思是进了商店之后,你要去哪个货架找什么东西(即请求的具体资源路径)。
|
||||
|
||||
**生活类比**:想象快递公司需要有**道路**和**运输车辆**:
|
||||
|
||||
- 道路可以是:高速公路(光纤)、普通公路(网线)、航空线路(无线电波)
|
||||
- 车辆可以是:卡车(有线传输)、飞机(无线传输)
|
||||
- 货物(数据)最终都要变成能在这些道路上运输的形式
|
||||
:::
|
||||
|
||||
**关键任务**:
|
||||
|
||||
- **定义物理设备标准**:RJ45 网线接口长什么样、光纤接口怎么接
|
||||
- **规定传输介质**:
|
||||
- 有线:双绞线(网线)、光纤、同轴电缆
|
||||
- 无线:Wi-Fi、蓝牙、4G/5G
|
||||
- **确定电气特性**:
|
||||
- 用多少电压代表 0 和 1?
|
||||
- 信号频率是多少?
|
||||
- 怎么编码(比如曼彻斯特编码)?
|
||||
|
||||
### 1.2 传输介质
|
||||
|
||||
**有线介质**:
|
||||
|
||||
| 类型 | 速度 | 距离 | 特点 | 用途 |
|
||||
| ------------ | ---------------- | -------- | ------------------------ | ------------------------ |
|
||||
| **双绞线** | 100Mbps - 10Gbps | 100m | 成本低,易安装,抗干扰一般 | 家庭、办公室网络 |
|
||||
| **光纤** | 1Gbps - 100Tbps | 几十公里 | 速度极快,抗干扰强,成本高 | 长距离、高带宽(跨海光缆) |
|
||||
| **同轴电缆** | 10Mbps - 1Gbps | 500m | 抗干扰好,但较粗 | 早期以太网、有线电视 |
|
||||
|
||||
::: tip 💡 为什么光纤这么快?
|
||||
光纤用**光**而不是电信号传输:
|
||||
|
||||
- 光的频率极高,能调制大量数据(就像用不同颜色的光同时传输)
|
||||
- 光在光纤中几乎不衰减,能传输几十公里
|
||||
- 不受电磁干扰(高压电线、雷电都不怕)
|
||||
|
||||
这就像用电信号寄快递(铜线)vs 用光速寄快递(光纤),速度差异是本质级别的。
|
||||
:::
|
||||
|
||||
**无线介质**:
|
||||
|
||||
| 类型 | 频段 | 速度 | 距离 | 用途 |
|
||||
| --------- | -------------------- | ------------------- | ------ | -------------------- |
|
||||
| **Wi-Fi** | 2.4GHz / 5GHz / 6GHz | 几十 Mbps - 几 Gbps | 几十米 | 家庭、办公室无线网络 |
|
||||
| **蓝牙** | 2.4GHz | 1-3 Mbps | 10m | 耳机、键鼠等短距设备 |
|
||||
| **4G/5G** | 700MHz - 39GHz | 10Mbps - 10Gbps | 几公里 | 移动网络 |
|
||||
|
||||
### 1.3 常见设备
|
||||
|
||||
**中继器(Repeater)**:
|
||||
|
||||
- **作用**:放大信号,延长传输距离
|
||||
- **生活类比**:快递中转站。快递车开了 500 公里需要加油、司机换班,中继器就是让信号"休息充电"的地方
|
||||
- **为什么需要**:电信号在铜线传输会衰减,传几百米就弱得识别不出了
|
||||
|
||||
**集线器(Hub)**:
|
||||
|
||||
- **作用**:多端口中继器,从一个口收到的信号复制到所有口
|
||||
- **缺点**:效率低,已被**交换机**取代
|
||||
- **生活类比**:一个大厅,一个人喊话,所有人都能听到,但不是喊给谁的
|
||||
浏览器第一步要做的,就是把这段“人类语言”拆解开,看看你到底想要什么。
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据链路层:车队管理
|
||||
## 2. 查找店铺地址 (DNS 解析)
|
||||
|
||||
### 2.1 基本概念
|
||||
网络世界的“快递员”(路由器设备)是不懂英文的,它们只认数字(也就是 **IP 地址**)。
|
||||
|
||||
::: tip 💡 数据链路层做什么?
|
||||
数据链路层负责在**直连的两个节点**间传输数据帧。
|
||||
它们需要知道对方的精确数字坐标!这就像快递员不知道“王府井百货”在哪,他必须先查地图,找到“北京市东城区王府井大街255号”这个确切的门牌号(比如 `142.250.66.4`)。
|
||||
|
||||
**生活类比**:快递公司的**车队管理**:
|
||||
|
||||
- 快递车从北京分拣中心开到天津分拣中心(点对点)
|
||||
- 车上有司机(负责驾驶)、装卸工(负责搬运)
|
||||
- 两边分拣中心之间有约定:"每天 8 点发车""用标准尺寸的快递箱"
|
||||
:::
|
||||
|
||||
**核心功能**:
|
||||
|
||||
- **物理地址寻址(MAC 地址)**:每个网卡都有全球唯一的身份证号
|
||||
- **帧的封装和解封装**:把网络层的数据包"装进车厢"
|
||||
- **错误检测**:通过 CRC 校验,发现数据是否损坏
|
||||
- **介质访问控制**:多个设备共享一条线时,谁先谁后?(比如 Wi-Fi 多台设备连一个路由器)
|
||||
|
||||
### 2.2 MAC 地址:设备的身份证
|
||||
|
||||
**MAC 地址格式**:`00:1A:2B:3C:4D:5E`
|
||||
|
||||
::: tip 💡 MAC 地址 vs IP 地址
|
||||
这是初学者最容易混淆的两个概念:
|
||||
|
||||
| 特性 | MAC 地址 | IP 地址 |
|
||||
| ------------ | ----------------------- | --------------------------- |
|
||||
| **作用范围** | 局域网内(同一个 Wi-Fi) | 全球互联网 |
|
||||
| **分配方式** | 网卡出厂时烧录,全球唯一 | 由网络管理员动态分配 |
|
||||
| **变化** | 一般不变(除非换网卡) | 经常变化(连不同 Wi-Fi 会变) |
|
||||
| **类比** | 身份证号(跟随你一生) | 家庭住址(搬家就变) |
|
||||
| **层级** | 数据链路层(第2层) | 网络层(第3层) |
|
||||
|
||||
**生活类比**:你要寄快递:
|
||||
|
||||
- **MAC 地址** = 收件人身份证号(唯一标识这个人)
|
||||
- **IP 地址** = 收件人家庭住址(用于路由)
|
||||
|
||||
快递员实际上需要"住址"才能送货,但身份证号能确保"这个人"是唯一的。
|
||||
:::
|
||||
|
||||
**查看你的 MAC 地址**:
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
ipconfig /all
|
||||
# 找到 "物理地址",类似: 00-1A-2B-3C-4D-5E
|
||||
|
||||
# macOS/Linux
|
||||
ifconfig
|
||||
# 找到 "ether",类似: 00:1a:2b:3c:4d:5e
|
||||
```
|
||||
|
||||
### 2.3 以太网帧:快递车厢的结构
|
||||
|
||||
**以太网帧**就是数据链路层的"快递车厢",有一套标准格式:
|
||||
|
||||
```
|
||||
+------------+----------+---------+-----+----------+
|
||||
| 目标 MAC | 源 MAC | 类型 | 数据 | FCS |
|
||||
| (6 bytes) | (6 bytes) | (2 bytes)| | (4 bytes) |
|
||||
+------------+----------+---------+-----+----------+
|
||||
```
|
||||
|
||||
::: tip 💡 逐行理解帧结构
|
||||
**目标 MAC (6字节)**:这帧数据给谁的?就像快递单上的收件人
|
||||
|
||||
**源 MAC (6字节)**:这帧数据谁发的?就像寄件人信息
|
||||
|
||||
**类型 (2字节)**:车厢里装的是什么?
|
||||
|
||||
- `0x0800` = IPv4 数据包
|
||||
- `0x0806` = ARP 请求(查询 MAC 地址)
|
||||
- `0x86DD` = IPv6 数据包
|
||||
|
||||
**数据(46-1500字节)**:实际要传输的内容,就是网络层的 IP 数据包
|
||||
|
||||
**FCS (4字节)**:帧校验序列。接收方用这个检查数据是否损坏,就像快递单上的"完好无损"签章
|
||||
:::
|
||||
|
||||
### 2.4 交换机:聪明的交通指挥
|
||||
|
||||
**交换机**是数据链路层的核心设备。
|
||||
|
||||
::: tip 💡 交换机 vs 集线器
|
||||
**集线器**:
|
||||
|
||||
- 收到数据后,简单地"广播"到所有端口
|
||||
- 所有设备都能看到,不是给自己的也得收下来再丢弃
|
||||
- 效率低,安全性差
|
||||
|
||||
**交换机**:
|
||||
|
||||
- **学习 MAC 地址**:记住哪个端口连了哪个 MAC 地址
|
||||
- **智能转发**:只把数据发到目标设备所在的端口
|
||||
- **效率高**:设备 A 和 B 通信,设备 C 不会收到
|
||||
:::
|
||||
|
||||
**交换机工作流程**:
|
||||
|
||||
1. **学习**:设备 A (MAC: 11:11...) 发数据给交换机端口1
|
||||
- 交换机记下:"11:11... 在端口1"
|
||||
|
||||
2. **转发**:设备 A 要发数据给设备 B (MAC: 22:22...)
|
||||
- 交换机查表:"22:22... 在端口3"
|
||||
- 只把数据从端口3 发出去
|
||||
|
||||
3. **广播**:如果交换机不知道目标 MAC 在哪(比如第一次通信)
|
||||
- 向所有端口(除了来源端口)广播
|
||||
- "谁是 22:22...?" 目标设备回应后,交换机学习到它的位置
|
||||
- **本地缓存**:浏览器会先翻翻自己的备忘录(看之前有没有访问过该网站)。
|
||||
- **DNS 系统**:如果在本地找不到,它就会向互联网的“查号台”(DNS 服务器)打电话询问:“请问 google.com 的数字地址是什么?”。一旦获得了对应的 IP 地址,浏览器的快递车就知道该往哪里开了。
|
||||
|
||||
---
|
||||
|
||||
## 3. 网络层:路由规划部
|
||||
## 3. 建立通话 (TCP 握手)
|
||||
|
||||
### 3.1 IP 地址:互联网的门牌号
|
||||
拿到了地址,浏览器不能直接冲过去,万一店今天没开门呢?所以,要先进行一次**“电话确认”**(这叫建立 TCP 连接)。为了确保通话稳定可靠,会有三次非常严谨的“确认打招呼”机制,行业里叫**三次握手 (Three-way Handshake)**:
|
||||
|
||||
::: tip 💡 IP 地址是什么?
|
||||
**IP 地址**就像互联网上的**家庭住址**,每台联网设备都需要一个。
|
||||
- **第一次握手 (浏览器)**:“喂,你好,我要来买东西,你在吗?” (SYN)
|
||||
- **第二次握手 (服务器)**:“我在的,欢迎光临!你也听得到我说话吗?” (SYN-ACK)
|
||||
- **第三次握手 (浏览器)**:“我也听到了!那我就要过来了!” (ACK)
|
||||
|
||||
**IPv4 地址格式**:`192.168.1.1`
|
||||
|
||||
- 32 位,通常用点分十进制表示
|
||||
- 分为**网络部分**(前3段)和**主机部分**(最后1段)
|
||||
- `192.168.1` 是网络号(这个小区)
|
||||
- `.1` 是主机号(这个小区的1号房)
|
||||
:::
|
||||
|
||||
**IP 地址分类(像城市规模)**:
|
||||
|
||||
| 类别 | 范围示例 | 网络数 | 每个网络主机数 | 用途 | 类比 |
|
||||
| -------- | --------------------- | --------- | -------------- | ---------------- | ---------- |
|
||||
| **A 类** | 1.0.0.0 - 126.x.x.x | 126 | 16,777,214 | 超大型网络(早期) | 特大城市 |
|
||||
| **B 类** | 128.0.0.0 - 191.x.x.x | 16,384 | 65,534 | 中型网络 | 中等城市 |
|
||||
| **C 类** | 192.0.0.0 - 223.x.x.x | 2,097,152 | 254 | 小型网络(最常见) | 小区、村庄 |
|
||||
|
||||
::: tip 💡 私有 IP 地址:内网 vs 外网
|
||||
有些 IP 地址段被保留为"私有",不能直接在互联网上使用:
|
||||
|
||||
| 类别 | 私有 IP 范围 | 为什么用私有 IP? |
|
||||
| -------- | ------------------------------- | -------------------- |
|
||||
| **A 类** | `10.0.0.0 - 10.255.255.255` | 大型企业内网 |
|
||||
| **B 类** | `172.16.0.0 - 172.31.255.255` | 中型企业内网 |
|
||||
| **C 类** | `192.168.0.0 - 192.168.255.255` | 家庭、小公司(最常见) |
|
||||
|
||||
**生活类比**:
|
||||
|
||||
- **私有 IP** = 你家的门牌号("3单元501室")
|
||||
- **公网 IP** = 你家在地图上的地址("XX市XX区XX路XX号")
|
||||
|
||||
快递员(互联网)只能送到公网地址(你家楼门口),然后需要"路由器/NAT"转换到你家的私有地址。
|
||||
:::
|
||||
|
||||
### 3.2 子网划分:把大楼分成多个单元
|
||||
|
||||
::: tip 💡 为什么要划分子网?
|
||||
想象一个公司:
|
||||
|
||||
- **不划分子网**:财务部、技术部、市场部都在 `192.168.1.0` 网段
|
||||
- 广播风暴:一个人发广播,所有人都能收到
|
||||
- 安全问题:技术部的开发服务器,市场部也能访问
|
||||
- 管理混乱:网络出问题,不知道是哪个部门的
|
||||
|
||||
**划分子网**:
|
||||
|
||||
- 财务部:`192.168.1.0/24`
|
||||
- 技术部:`192.168.2.0/24`
|
||||
- 市场部:`192.168.3.0/24`
|
||||
|
||||
各部门隔离,广播不出部门,管理更清晰。
|
||||
:::
|
||||
|
||||
**子网掩码的作用**:
|
||||
|
||||
子网掩码用来区分 IP 地址的哪部分是"网络号",哪部分是"主机号"。
|
||||
|
||||
```
|
||||
IP: 192.168.1.10
|
||||
掩码: 255.255.255.0
|
||||
-----------------------
|
||||
网络号: 192.168.1.0 (前3段)
|
||||
主机号: .10 (最后1段)
|
||||
```
|
||||
|
||||
**CIDR 表示法**:`192.168.1.0/24`
|
||||
|
||||
- `/24` 表示前 24 位是网络位
|
||||
- 剩余 8 位是主机位(2^8 - 2 = 254 个可用 IP)
|
||||
|
||||
<CFSubnetCalculator />
|
||||
|
||||
### 3.3 路由器:GPS 导航
|
||||
|
||||
**路由器**是网络层的核心设备,负责"规划最佳路线"。
|
||||
|
||||
::: tip 💡 路由器怎么工作?
|
||||
**生活类比**:GPS 导航软件
|
||||
|
||||
- 你输入:"从北京天安门到上海外滩"
|
||||
- GPS 查询地图数据库,规划出最佳路线
|
||||
- 路线可能是:"北京 → 天津 → 济南 → 南京 → 上海"
|
||||
|
||||
**路由器的工作**:
|
||||
|
||||
1. 收到数据包,查看目标 IP 地址
|
||||
2. 查询**路由表**(路由器的"地图数据库")
|
||||
3. 选择最佳路径:"下一站该去哪个路由器?"
|
||||
4. 转发到下一跳
|
||||
:::
|
||||
|
||||
**路由表示例**:
|
||||
|
||||
```
|
||||
目标网络 子网掩码 网关 接口
|
||||
192.168.1.0 255.255.255.0 0.0.0.0 eth0
|
||||
192.168.2.0 255.255.255.0 192.168.1.2 eth0
|
||||
0.0.0.0 0.0.0.0 192.168.1.1 eth0 (默认网关)
|
||||
```
|
||||
|
||||
::: tip 💡 理解路由表
|
||||
**第1行**:"发往 192.168.1.0 网段的包,直接从 eth0 接口发出去"(本地网络,不需要网关)
|
||||
|
||||
**第2行**:"发往 192.168.2.0 网段的包,发给 192.168.1.2(它是这个网络的'门')"
|
||||
|
||||
**第3行(默认网关)**:"不知道怎么走的包,全部发给 192.168.1.1(它连接互联网,会继续帮你转发)"
|
||||
|
||||
这就像你去外地:
|
||||
|
||||
- 在本地:走路就到(直接路由)
|
||||
- 去隔壁城市:坐大巴(走网关)
|
||||
- 去国外:先到机场,再转机(默认网关 → 层层转发)
|
||||
:::
|
||||
|
||||
### 3.4 ICMP:网络诊断工具
|
||||
|
||||
**ICMP (Internet Control Message Protocol)** 用于网络诊断,最常用的就是 `ping` 命令。
|
||||
|
||||
**Ping 命令**:
|
||||
|
||||
```bash
|
||||
ping google.com
|
||||
|
||||
# 输出示例
|
||||
PING google.com (142.250.185.238): 56 data bytes
|
||||
64 bytes from 142.250.185.238: icmp_seq=0 ttl=117 time=12.4 ms
|
||||
64 bytes from 142.250.185.238: icmp_seq=1 ttl=117 time=11.8 ms
|
||||
```
|
||||
|
||||
::: tip 💡 理解 ping 的输出
|
||||
**`64 bytes`**:数据包大小(64 字节)
|
||||
|
||||
**`icmp_seq=0`**:这是第 0 个包(序列号)
|
||||
|
||||
**`ttl=117`**:Time To Live(生存时间)
|
||||
|
||||
- 每经过一个路由器减 1
|
||||
- 防止数据包在网络中无限循环
|
||||
- 117 表示这个包经过了 255-117=138 个路由器
|
||||
|
||||
**`time=12.4 ms`**:往返时间(RTT, Round Trip Time)
|
||||
|
||||
- 你的电脑发送请求 → google.com 收到 → google.com 回应 → 你的电脑收到
|
||||
- 整个过程花了 12.4 毫秒
|
||||
- 数值越小,网络延迟越低,网速越快
|
||||
:::
|
||||
经过这三次确认,双方都知道了彼此的听力和表达能力都没问题,一条稳定可靠的通信通道就正式建立了。
|
||||
|
||||
---
|
||||
|
||||
## 4. 传输层:可靠送达 vs 快速送达
|
||||
## 4. 购买商品 (HTTP 请求与响应)
|
||||
|
||||
### 4.1 端口:应用的门牌号
|
||||
通道建好后,业务正式开始。
|
||||
|
||||
::: tip 💡 为什么需要端口号?
|
||||
想象一台服务器:
|
||||
|
||||
- **只有 IP 地址**:数据包到了服务器,服务器不知道给哪个程序
|
||||
- Web 服务器要?
|
||||
- 邮件服务器要?
|
||||
- 数据库服务器要?
|
||||
|
||||
**端口号**就像"公司里的部门号":
|
||||
|
||||
- IP: 公司地址(XX 市XX 路 XX 号)
|
||||
- 端口: 部门(301 财务部、302 技术部、303 市场部)
|
||||
|
||||
数据包到了公司,前台(操作系统)根据"部门号"(端口)转发给对应部门(应用程序)。
|
||||
:::
|
||||
|
||||
**端口号范围**:
|
||||
|
||||
| 范围 | 类型 | 示例 | 需要权限? |
|
||||
| --------------- | -------- | ----------------------------- | --------------------------------- |
|
||||
| **0-1023** | 系统端口 | 80(HTTP)、443(HTTPS)、22(SSH) | ✅ 需要(防止普通用户占用关键服务) |
|
||||
| **1024-49151** | 注册端口 | 3306(MySQL)、5432(PostgreSQL) | ❌ 不需要 |
|
||||
| **49152-65535** | 动态端口 | 客户端临时使用 | ❌ 不需要 |
|
||||
|
||||
**常见端口速查**:
|
||||
|
||||
| 端口 | 服务 | 用途 |
|
||||
| --------- | ---------- | --------------- |
|
||||
| **21** | FTP | 文件传输 |
|
||||
| **22** | SSH | 远程登录(安全) |
|
||||
| **80** | HTTP | 网页(不安全) |
|
||||
| **443** | HTTPS | 网页(安全,加密) |
|
||||
| **3306** | MySQL | 数据库 |
|
||||
| **5432** | PostgreSQL | 数据库 |
|
||||
| **6379** | Redis | 缓存数据库 |
|
||||
| **27017** | MongoDB | 数据库 |
|
||||
|
||||
### 4.2 TCP vs UDP:挂号信 vs 平信
|
||||
|
||||
<CFTcpUdpComparison />
|
||||
|
||||
**选择建议**:
|
||||
|
||||
| 场景 | 选择 | 原因 |
|
||||
| ------------------ | ------- | ----------------------------------------- |
|
||||
| **邮件、文件传输** | **TCP** | 不能丢数据,一个字节错误都可能导致文件损坏 |
|
||||
| **视频、直播** | **UDP** | 实时性优先,丢几帧没关系,但不能卡顿 |
|
||||
| **网页浏览** | **TCP** | 可靠性重要,网页内容必须完整 |
|
||||
| **在线游戏** | **UDP** | 速度优先,位置信息晚到比没到好 |
|
||||
|
||||
::: tip 💡 深入理解:TCP 为什么可靠?
|
||||
TCP 通过以下机制保证可靠:
|
||||
|
||||
1. **三次握手**:确保双方都能发送和接收
|
||||
2. **序列号**:每个字节都有编号,丢包能发现
|
||||
3. **确认应答**:收到数据必须回复 ACK,没收到就重传
|
||||
4. **流量控制**:接收方告诉发送方"我的缓冲区快满了,慢点发"
|
||||
5. **拥塞控制**:网络拥堵时,降低发送速度,避免"堵死"
|
||||
|
||||
这就像寄挂号信:
|
||||
|
||||
- 要签收(ACK)
|
||||
- 丢了邮政局会重传
|
||||
- 太多信件会积压,需要控制发送速度
|
||||
:::
|
||||
|
||||
### 4.3 TCP 三次握手:建立可靠连接
|
||||
|
||||
```
|
||||
客户端 服务器
|
||||
| |
|
||||
| -------- SYN(seq=x) ---------> | 第1次:你好,我想和你通信(SYN)
|
||||
| | (x 是随机数,防止伪造)
|
||||
| |
|
||||
| <--- SYN-ACK(seq=y, ack=x+1) ---| 第2次:收到!我也想和你通信(SYN)
|
||||
| | 我收到了你的 x,所以 ack=x+1
|
||||
| |
|
||||
| -------- ACK(ack=y+1) --------> | 第3次:我收到了你的 y,所以 ack=y+1
|
||||
| | 连接建立成功!
|
||||
```
|
||||
|
||||
::: tip 💡 为什么需要三次,不是两次?
|
||||
想象打电话:
|
||||
|
||||
- **A**:你好!(SYN)
|
||||
- **B**:你好!(SYN-ACK) —- 此时 B 确认了 A 能收到,但 A 还不确定 B 能不能收到
|
||||
- **A**:我听到了!(ACK) —- 现在双方都知道对方能收能发
|
||||
|
||||
如果只有两次:
|
||||
|
||||
- A 发 SYN
|
||||
- B 回 SYN-ACK
|
||||
- 连接建立...但 B 不知道 A 有没有收到 SYN-ACK!如果 A 没收到,会重复发 SYN,但 B 以为已经建立连接,会出现问题
|
||||
:::
|
||||
- **浏览器(买家)提交订单**:浏览器会打包一份极其规范的订单表格(**HTTP 请求报文**),里面写着:“老板,请给我拿一份你的主页 HTML 文件,我是用 Chrome 浏览器来访问的哦。”
|
||||
- **服务器(卖家)根据订单发货**:位于地球另一端的 Google 服务器收到请求后,立刻开始在仓库里配货,生成网页的 HTML 代码,然后打包成包裹(**HTTP 响应报文**),发回给你的浏览器。包裹外面还会贴个标签“200 OK”,意思是“交易成功,你要的货全齐了”。
|
||||
|
||||
---
|
||||
|
||||
## 5. 应用层:具体的业务
|
||||
## 5. 拆盒组装 (浏览器渲染)
|
||||
|
||||
### 5.1 HTTP/HTTPS:网页的对话协议
|
||||
最后一步,货物送到了你的电脑。但发过来的只是一堆代码(HTML、CSS、JavaScript),这就好比你网购买了一箱乐高积木,还需要自己组装:
|
||||
|
||||
**HTTP (HyperText Transfer Protocol)** 是浏览器和服务器之间的"对话规则"。
|
||||
1. **看说明书 (解析 HTML)**:浏览器先把 HTML 代码解读出来,拼装成网页的骨架(DOM 树)。
|
||||
2. **涂抹颜色 (解析 CSS)**:然后检查 CSS 代码,看看字体要多大、按钮是什么颜色,给网页穿上漂亮的外衣(CSSOM 树)。
|
||||
3. **计算布局并拼装 (Layout & Paint)**:浏览器计算好每个元素在屏幕上的确切位置,用画笔把它们画在你的显示器上。
|
||||
4. **注入灵魂 (执行 JavaScript)**:最后,各种能点击、能滑动的交互效果都通过 JavaScript 激活。
|
||||
|
||||
| 特性 | HTTP | HTTPS |
|
||||
| ---------- | ------------------------ | ----------------------------- |
|
||||
| **加密** | ❌ 否(明文,任何人都能看) | ✅ 是(TLS/SSL 加密) |
|
||||
| **端口** | 80 | 443 |
|
||||
| **安全性** | 低(密码、账号会被窃取) | 高(即使被拦截,看到的也是乱码) |
|
||||
| **性能** | 略快(无加密开销) | 略慢(加密解密需要时间) |
|
||||
| **SEO** | 不友好(搜索引擎会降权) | 友好(搜索引擎优先收录 HTTPS) |
|
||||
|
||||
**HTTP 请求方法**:
|
||||
|
||||
| 方法 | 描述 | 生活类比 | 示例 |
|
||||
| ---------- | ---------------------- | ---------------------------- | ---------------------- |
|
||||
| **GET** | 获取资源 | "我要看这个商品的详情" | 查看网页、加载图片 |
|
||||
| **POST** | 提交数据 | "我要下单,这是我的收货信息" | 登录、注册、提交表单 |
|
||||
| **PUT** | 更新资源(整体替换) | "我要完整更新这个商品的信息" | 修改用户资料(全部字段) |
|
||||
| **PATCH** | 部分更新 | "我只想改商品的名称" | 修改用户资料(只改名字) |
|
||||
| **DELETE** | 删除资源 | "我要删除这个订单" | 删除文章、删除评论 |
|
||||
| **HEAD** | 只获取响应头(不要内容) | "这个文件还在吗?有多大?" | 检查资源是否存在 |
|
||||
|
||||
**HTTP 状态码**(服务器给你的"回复"):
|
||||
|
||||
```
|
||||
2xx 成功
|
||||
- 200 OK:请求成功,这是你要的内容
|
||||
- 201 Created:创建成功(比如注册新用户)
|
||||
|
||||
3xx 重定向
|
||||
- 301 Moved Permanently:永久搬家了,请用新地址
|
||||
- 302 Found:暂时搬迁,请访问新地址
|
||||
|
||||
4xx 客户端错误(你发的问题)
|
||||
- 400 Bad Request:请求格式错误,服务器看不懂
|
||||
- 401 Unauthorized:未授权,请先登录
|
||||
- 403 Forbidden:禁止访问,即使登录也不行
|
||||
- 404 Not Found:资源不存在(网址错了?)
|
||||
|
||||
5xx 服务器错误(服务器的问题)
|
||||
- 500 Internal Server Error:服务器内部出错了
|
||||
- 502 Bad Gateway:网关错误,服务器连不上后端
|
||||
- 503 Service Unavailable:服务暂时不可用(过载或维护)
|
||||
```
|
||||
|
||||
### 5.2 DNS:互联网的地址簿
|
||||
|
||||
**DNS (Domain Name System)** 域名系统,把人类可读的域名转换成机器可读的 IP 地址。
|
||||
|
||||
::: tip 💡 为什么需要 DNS?
|
||||
**没有 DNS 的世界**:
|
||||
|
||||
- 你需要记住所有网站的 IP 地址
|
||||
- 访问百度:`https://110.242.68.66`(你能记住吗?)
|
||||
- IP 地址会变(服务器迁移),你需要重新记住
|
||||
|
||||
**有 DNS 的世界**:
|
||||
|
||||
- 记住域名:`baidu.com`
|
||||
- DNS 帮你转换:`baidu.com` → `110.242.68.66`
|
||||
- IP 变了?更新 DNS 记录就行,域名不用变
|
||||
:::
|
||||
|
||||
**DNS 查询过程**:
|
||||
|
||||
```
|
||||
你(浏览器)
|
||||
↓ 问:baidu.com 的 IP 是多少?
|
||||
本地 DNS 服务器(你的网络运营商,如电信/联通)
|
||||
↓ 不知道? 问:
|
||||
根域名服务器(全球13组,管理所有顶级域)
|
||||
↓ 告诉:去问 .cn 的管理者
|
||||
顶级域名服务器(Verisign 管理 .cn)
|
||||
↓ 告诉:去问 baidu.com 的管理者
|
||||
权威 DNS 服务器(Baidu 自己的 DNS)
|
||||
↓ 告诉:baidu.com 的 IP 是 110.242.68.66
|
||||
返回 IP 地址给浏览器
|
||||
```
|
||||
|
||||
**DNS 记录类型**:
|
||||
|
||||
| 类型 | 用途 | 示例 |
|
||||
| --------- | ---------------- | ------------------------------------------------------ |
|
||||
| **A** | 域名 → IPv4 地址 | `www.example.com → 93.184.216.34` |
|
||||
| **AAAA** | 域名 → IPv6 地址 | `www.example.com → 2606:2800:220:1:248:1893:25c8:1946` |
|
||||
| **CNAME** | 别名 | `www.baidu.com → a.baidu.com`(多个域名指向同一个 IP) |
|
||||
| **MX** | 邮件服务器 | `@example.com → mail.example.com`(邮件发到哪里) |
|
||||
**只要短短的几百毫秒,所有的步骤就已全部完成,你也就看到了那个熟悉的页面!**
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结:网络五层模型核心要点
|
||||
## 总结:从微观到宏观
|
||||
|
||||
| 层级 | 核心概念 | 关键技术 | 生活类比 |
|
||||
| -------------- | ------------------- | -------------------- | ---------------------------------- |
|
||||
| **应用层** | 应用程序之间的通信 | HTTP, FTP, SMTP, DNS | 具体业务(寄快递、发邮件、浏览网页) |
|
||||
| **传输层** | 端到端的可靠传输 | TCP(可靠), UDP(快速) | 快递方式(挂号信 vs 平信) |
|
||||
| **网络层** | 路由选择,寻址 | IP, 路由器, ICMP | GPS 导航,规划路线 |
|
||||
| **数据链路层** | 点对点传输,MAC 寻址 | 以太网, 交换机, MAC | 车队管理,车辆之间通信 |
|
||||
| **物理层** | 实际的物理传输 | 光纤, 网线, 无线电波 | 道路和运输工具 |
|
||||
如果我们把目光再拉远一点,整个网络通讯的本质,就是在做**接力跑和翻译**:
|
||||
|
||||
**学习建议**:
|
||||
- 我们上面看到的这五步,大多是发生在你眼前的**应用程序**层面的事情。
|
||||
- 在肉眼看不见的底层,刚才那个充满代码的 HTML 包裹,会被切分成无数块极小的碎片(数据包)。这些碎片顺着你家墙上的网线、海底的万兆光缆,像接力棒一样在各种路由器之间传递。
|
||||
- 最终,这一切碎片完好无损地抵达,并在哪怕是几十个毫秒的时间里,化成你屏幕上的绚丽像素。
|
||||
|
||||
- ✅ **从应用层往下学**:你每天都在用 HTTP,DNS,从熟悉的开始
|
||||
- ✅ **多用工具**:ping, traceroute, Wireshark,观察实际网络
|
||||
- ✅ **理解协议细节**:阅读 RFC 文档(比如 RFC 791 定义 IP)
|
||||
- ✅ **抓包分析**:用 Wireshark 观察 TCP 三次握手、HTTP 请求
|
||||
- ✅ **关注安全**:了解 DDoS、中间人攻击等常见威胁
|
||||
|
||||
掌握计算机网络,你就能理解互联网的运作原理,写出更高效的网络应用!
|
||||
|
||||
---
|
||||
|
||||
## 附录:名词速查表
|
||||
|
||||
| 名词 | 英文 | 用人话解释 |
|
||||
| --------------- | ----------------------------- | ------------------------------------------- |
|
||||
| **OSI 模型** | Open Systems Interconnection | 七层网络模型(理论标准) |
|
||||
| **TCP/IP 模型** | - | 实际使用的四层/五层模型 |
|
||||
| **MAC 地址** | Media Access Control | 网卡的物理地址,全球唯一,像身份证 |
|
||||
| **IP 地址** | Internet Protocol | 设备在互联网上的逻辑地址,像住址 |
|
||||
| **子网掩码** | Subnet Mask | 区分 IP 地址的网络部分和主机部分 |
|
||||
| **端口** | Port | 应用程序的"门牌号",区分同一台设备的不同服务 |
|
||||
| **TCP** | Transmission Control Protocol | 可靠传输协议,三次握手,不丢包 |
|
||||
| **UDP** | User Datagram Protocol | 快速传输协议,不保证可靠,可能丢包 |
|
||||
| **DNS** | Domain Name System | 域名系统,把域名转成 IP 地址 |
|
||||
| **HTTP** | HyperText Transfer Protocol | 超文本传输协议,网页通信规则 |
|
||||
| **HTTPS** | HTTP Secure | 加密的 HTTP,更安全 |
|
||||
| **路由器** | Router | 网络层设备,规划路线,连接不同网络 |
|
||||
| **交换机** | Switch | 数据链路层设备,智能转发数据帧 |
|
||||
| **TTL** | Time To Live | 生存时间,防止数据包无限循环 |
|
||||
| **RTT** | Round Trip Time | 往返时间,数据从发送到接收确认的时间 |
|
||||
这就是计算机网络的神奇魅力!
|
||||
|
||||
@@ -1,255 +1,99 @@
|
||||
# 操作系统(进程 / 内存 / 文件系统)
|
||||
# 操作系统:给电脑请个"大管家"
|
||||
|
||||
::: tip 🎯 核心问题
|
||||
**操作系统是做什么的?** 你可能每天都在用 Windows、macOS 或 Linux,但你知道它到底在忙什么吗?为什么需要它?没有它电脑还能用吗?本章带你理解操作系统的三大核心职责:管理进程、管理内存、管理文件。
|
||||
**有了完美的 CPU 和无限的内存,电脑就能直接用了吗?**
|
||||
在上一章,我们见证了晶体管如何组合成强大的 CPU。但其实,如果直接使用这些冷冰冰的硬件,哪怕只是想在屏幕上打出一个字母,你都需要手写几百行晦涩的机器指令。
|
||||
|
||||
为了不让大家在每次用电脑时都被逼疯,前辈们创造了一个夹在“硬件”和“你”之间的超级管家——**操作系统(Operating System, 简称 OS)**。本章我们不谈深奥的理论,只聊聊这个大管家是怎么通过三大“障眼法”,把复杂的硬件调教得服服帖帖的。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 0. 全景图:操作系统的角色
|
||||
## 0. 承上启下:如果没有操作系统会怎样?
|
||||
|
||||
想象你开了一家餐厅。你需要:
|
||||
- **安排员工工作**:谁做菜、谁端盘子、谁收银(进程管理)
|
||||
- **管理厨房空间**:冰箱放什么、操作台怎么分配(内存管理)
|
||||
- **整理仓库物资**:食材怎么存放、怎么找(文件系统)
|
||||
上一章我们提到,CPU 是一个不知疲倦的无情计算机器,通电后就会一行一行地执行指令。
|
||||
|
||||
操作系统就是电脑的"餐厅经理",它负责协调所有资源,让程序能顺利运行。
|
||||
但这带来了几个现实的灾难:
|
||||
1. **CPU 独占危机**:CPU 一次只能干一件事。如果你正在听歌,想切出去看个网页?抱歉,没有操作系统的调度,你的电脑必须停下音乐,才能去加载网页。
|
||||
2. **内存踩踏事故**:微信和游戏都在使用内存。如果没有保安管理,游戏一不小心把数据写到了微信的内存地盘,微信当场崩溃。
|
||||
3. **硬盘迷宫**:硬盘本质上只是一张密密麻麻刻满 0 和 1 的巨大光盘。要想找到你昨天存的照片,你必须准确记住它存放在第 12345 圈磁道的第 678 个扇区。
|
||||
|
||||
**操作系统的三大核心职责:**
|
||||
|
||||
| 职责 | 管理对象 | 核心问题 | 类比 |
|
||||
|------|---------|---------|------|
|
||||
| **进程管理** | CPU 时间 | 谁先用 CPU?用多久? | 员工排班 |
|
||||
| **内存管理** | 内存空间 | 程序放哪里?怎么不冲突? | 厨房空间分配 |
|
||||
| **文件系统** | 磁盘数据 | 数据怎么存?怎么找? | 仓库物资管理 |
|
||||
|
||||
::: tip 📊 逐行解读这张表
|
||||
**进程管理**:CPU 是最宝贵的资源,操作系统要决定哪个程序先用、用多久。就像餐厅经理安排员工轮班,不能让所有人同时挤在厨房里。
|
||||
|
||||
**内存管理**:内存是程序的"工作台",操作系统要给每个程序分配空间,还要保证它们互不干扰。就像厨房空间有限,要合理分配给不同的厨师。
|
||||
|
||||
**文件系统**:磁盘是"仓库",操作系统要把数据有序地存进去,需要时能快速找到。就像仓库管理员整理货架,按类别、编号存放。
|
||||
:::
|
||||
为了解决这些噩梦,操作系统诞生了。它对外提供了一套优雅的“幻觉”,这就是它的三大核心魔法:**进程(管理 CPU)**、**虚拟内存(管理内存)** 和 **文件系统(管理硬盘)**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 进程管理:程序的"分身术"
|
||||
## 1. 进程管理:制造“同时运行”的幻觉
|
||||
|
||||
### 1.1 什么是进程?
|
||||
你平时用电脑,常常是一边挂着微信,一边听着音乐,还能一边打字。但如果你买的电脑其实只有一个 CPU 核心,它是怎么同时做这三件事的?
|
||||
|
||||
答案是:**它并没有同时做**。是操作系统在进行疯狂的“时间管理”。
|
||||
|
||||
<ProcessDemo />
|
||||
|
||||
::: tip 💡 程序 vs 进程
|
||||
这是初学者最容易混淆的概念:
|
||||
::: tip 💡 核心原理解析:时间片轮转(Time Slicing)
|
||||
操作系统把 CPU 的时间切成了极其微小的片段(比如 10 毫秒)。
|
||||
- 第 1-10 毫秒:让 CPU 去执行**微信**的接收消息逻辑。
|
||||
- 第 11-20 毫秒:把微信强制暂停,让 CPU 去执行**音乐**的播放逻辑。
|
||||
- 第 21-30 毫秒:把音乐暂停,让 CPU 去响应你的**键盘打字**。
|
||||
|
||||
| 概念 | 定义 | 类比 | 特点 |
|
||||
|------|------|------|------|
|
||||
| **程序** | 静态的代码文件 | 菜谱 | 存在磁盘上,不会动 |
|
||||
| **进程** | 程序的运行实例 | 正在按菜谱做菜 | 在内存中运行,会变化 |
|
||||
因为切换的速度实在太快了(一秒钟切换成百上千次),在人类迟钝的感知中,就觉得这三个软件是“同时”在运行的。
|
||||
|
||||
**关键区别**:
|
||||
- 一个程序可以启动多个进程(比如打开多个浏览器窗口)
|
||||
- 每个进程有独立的内存空间,互不干扰
|
||||
- 进程有生命周期:创建、运行、等待、终止
|
||||
在操作系统的术语里,运行中的程序就被称为**进程(Process)**。操作系统就是这群进程的冷酷无情的排班经理。
|
||||
:::
|
||||
|
||||
### 1.2 进程的状态
|
||||
|
||||
进程在运行过程中会在不同状态之间切换:
|
||||
|
||||
| 状态 | 含义 | 什么时候进入 | 类比 |
|
||||
|------|------|-------------|------|
|
||||
| **就绪 (Ready)** | 准备好运行,等 CPU | 进程刚创建,或从等待恢复 | 员工在休息室等排班 |
|
||||
| **运行 (Running)** | 正在 CPU 上执行 | 被调度器选中 | 员工正在工作 |
|
||||
| **等待 (Waiting)** | 等待 I/O 或其他资源 | 需要读磁盘、等网络 | 员工在等食材送达 |
|
||||
| **终止 (Terminated)** | 运行结束 | 程序退出或出错 | 员工下班 |
|
||||
|
||||
### 1.3 进程调度:谁先用 CPU?
|
||||
|
||||
::: tip 💡 为什么需要调度?
|
||||
CPU 核心数有限,但进程可能有几十上百个。操作系统需要决定:
|
||||
- 哪个进程先运行?
|
||||
- 运行多久?
|
||||
- 什么时候切换?
|
||||
|
||||
这就是**进程调度**要解决的问题。
|
||||
:::
|
||||
|
||||
**常见调度算法:**
|
||||
|
||||
| 算法 | 思路 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| **先来先服务 (FCFS)** | 谁先到谁先运行 | 简单公平 | 短任务可能等很久 |
|
||||
| **短作业优先 (SJF)** | 短任务优先 | 平均等待时间最短 | 需要预知任务长度 |
|
||||
| **时间片轮转 (RR)** | 每人运行一小段时间 | 公平,响应快 | 切换开销大 |
|
||||
| **优先级调度** | 重要任务优先 | 重要任务响应快 | 可能导致低优先级任务饿死 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 内存管理:程序的"工作台"
|
||||
## 2. 内存管理:给每个程序画个“海市蜃楼”
|
||||
|
||||
### 2.1 为什么需要内存管理?
|
||||
解决了 CPU 轮流用的问题,接下来是存放数据的内存。如果所有的进程都挤在同一块物理内存里,很容易发生互相干扰和偷看数据的危险。
|
||||
|
||||
操作系统的第二大魔法,叫作**虚拟内存(Virtual Memory)**。
|
||||
|
||||
<MemoryDemo />
|
||||
|
||||
::: tip 💡 如果没有内存管理会怎样?
|
||||
想象一个没有管理的厨房:
|
||||
::: tip 💡 核心原理解析:内存映射
|
||||
操作系统对每一个启动的进程撒了一个弥天大谎:“嘿,你独占了整整 4GB 的纯净内存空间,随便用!”(这就是**虚拟内存**)。
|
||||
|
||||
- **冲突**:两个厨师同时用同一个灶台,菜都糊了
|
||||
- **浪费**:有人占了整个厨房,其他人没地方做饭
|
||||
- **安全问题**:有人偷吃了别人的食材
|
||||
但实际上,当进程往这个“虚拟空间”里放东西时,操作系统的底层会拿出一个**映射表(页表)**,偷偷把数据塞进**真实物理内存(Physical Memory)**中各种零碎、不连续的角落里。
|
||||
|
||||
操作系统通过**内存管理**解决这些问题:
|
||||
- 给每个进程分配独立的内存空间
|
||||
- 防止进程互相干扰(内存保护)
|
||||
- 高效利用有限的内存资源
|
||||
**这么做有两个巨大的好处:**
|
||||
1. **绝对安全**:微信永远只能看到自己的虚拟空间,它根本不知道音乐的数据在物理内存的哪个角落,自然就不会发生“踩踏”。
|
||||
2. **碎片利用**:物理内存就算被用得像狗皮膏药一样稀碎,映射给进程的虚拟空间依然是连续且整齐的。
|
||||
:::
|
||||
|
||||
### 2.2 虚拟内存:让每个进程都"以为"自己独占内存
|
||||
|
||||
::: tip 💡 什么是虚拟内存?
|
||||
**虚拟内存**是操作系统的一个"魔术":
|
||||
|
||||
- 每个进程都以为自己有 4GB(或更多)的内存空间
|
||||
- 实际上物理内存可能只有 8GB、16GB
|
||||
- 操作系统通过"映射"把虚拟地址转换成物理地址
|
||||
|
||||
**生活类比**:想象一个酒店:
|
||||
- 每个客人都以为自己独占整个酒店(虚拟空间)
|
||||
- 实际上酒店只有 100 间房(物理内存)
|
||||
- 前台(操作系统)负责分配房间、记录谁住哪里
|
||||
:::
|
||||
|
||||
**虚拟内存的好处:**
|
||||
|
||||
| 好处 | 说明 | 为什么重要 |
|
||||
|------|------|-----------|
|
||||
| **隔离保护** | 进程间内存互不干扰 | 一个崩溃不影响其他 |
|
||||
| **内存扩展** | 用磁盘当内存用 | 可以运行比物理内存大的程序 |
|
||||
| **简化编程** | 不用关心物理地址 | 程序员写代码更简单 |
|
||||
|
||||
### 2.3 内存分配策略
|
||||
|
||||
当进程需要内存时,操作系统如何分配?
|
||||
|
||||
| 策略 | 思路 | 特点 |
|
||||
|------|------|------|
|
||||
| **首次适应** | 找到第一个够大的空闲块 | 速度快 |
|
||||
| **最佳适应** | 找最小的够大的空闲块 | 内存利用率高 |
|
||||
| **最坏适应** | 找最大的空闲块 | 减少小碎片 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 文件系统:数据的"档案柜"
|
||||
## 3. 文件系统:把“荒地”变成“档案馆”
|
||||
|
||||
### 3.1 什么是文件系统?
|
||||
如果你买了一块崭新的硬盘,它里面其实是一片荒芜的存储单元。如果你想存一张照片,硬盘硬件只会问你:“请告诉我你要存在第几个字节地址?”这显然反人类。
|
||||
|
||||
操作系统的第三大魔法是**文件系统(File System)**,它为你构建了我们最熟悉的:文件夹(目录)和文件的概念。
|
||||
|
||||
<FilesystemDemo />
|
||||
|
||||
::: tip 💡 文件系统是什么?
|
||||
**文件系统**是操作系统管理磁盘数据的方式。
|
||||
|
||||
**生活类比**:想象一个图书馆:
|
||||
- 书架 = 磁盘
|
||||
- 书 = 文件
|
||||
- 目录卡片 = inode
|
||||
- 分类编号 = 路径
|
||||
|
||||
没有文件系统,磁盘就是一堆杂乱的数据。有了文件系统,我们可以:
|
||||
- 用"路径"找到文件(如 `/home/user/document.txt`)
|
||||
- 创建、删除、修改文件
|
||||
- 控制谁能访问哪些文件
|
||||
:::
|
||||
|
||||
### 3.2 inode:文件的"身份证"
|
||||
|
||||
::: tip 💡 inode 是什么?
|
||||
每个文件都有一个 **inode**(索引节点),记录了文件的元数据:
|
||||
|
||||
| 信息 | 说明 |
|
||||
|------|------|
|
||||
| inode 编号 | 文件的唯一标识 |
|
||||
| 文件大小 | 多少字节 |
|
||||
| 权限 | 谁能读写 |
|
||||
| 时间戳 | 创建、修改、访问时间 |
|
||||
| 数据块位置 | 文件内容存在哪些磁盘块 |
|
||||
|
||||
**关键理解**:
|
||||
- 文件名不在 inode 里!文件名只是目录中的一个条目
|
||||
- 一个文件可以有多个名字(硬链接)
|
||||
- 删除文件只是删除目录项,inode 可能还在
|
||||
:::
|
||||
|
||||
### 3.3 常见文件系统
|
||||
|
||||
| 文件系统 | 操作系统 | 特点 |
|
||||
|---------|---------|------|
|
||||
| **NTFS** | Windows | 支持大文件、权限控制 |
|
||||
| **APFS** | macOS | 加密、快照、高效 |
|
||||
| **ext4** | Linux | 稳定、高效、广泛使用 |
|
||||
| **FAT32** | 通用 | 兼容性好,但单文件最大 4GB |
|
||||
| **exFAT** | 通用 | 支持大文件,适合 U 盘 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 进程、内存、文件系统的协作
|
||||
|
||||
这三个子系统是如何配合工作的?让我们看一个完整的例子:
|
||||
|
||||
**场景:打开一个文档文件**
|
||||
|
||||
```
|
||||
1. 用户双击文件
|
||||
↓
|
||||
2. 文件系统:根据路径找到 inode,读取文件内容
|
||||
↓
|
||||
3. 进程管理:创建新进程(文档编辑器),分配 PID
|
||||
↓
|
||||
4. 内存管理:为新进程分配内存,加载程序代码和数据
|
||||
↓
|
||||
5. 进程运行:编辑器进程读取文件内容,显示在屏幕上
|
||||
```
|
||||
|
||||
::: tip 💡 理解这个流程
|
||||
每一步都涉及操作系统的核心功能:
|
||||
|
||||
1. **文件系统**:负责"找到文件"
|
||||
2. **进程管理**:负责"启动程序"
|
||||
3. **内存管理**:负责"给程序分配空间"
|
||||
|
||||
这三者紧密协作,才能完成一个看似简单的"打开文件"操作。
|
||||
::: tip 💡 核心原理解析:从地址到路径
|
||||
文件系统本质上是一个超级大型的“翻译官”加“账本”:
|
||||
1. **账本功能**:它悄悄地把硬盘切分成无数个小块(Block),然后用一个账本记录下来“哪几个小块现在是空的可以存数据,哪几个小块已经存了东西”。
|
||||
2. **翻译功能**:当你双击一层层文件夹,打开 `D盘/照片/宠物.jpg` 时,并不是硬盘真的长出了树枝一样的结构。而是文件系统在它的账本里疯狂翻阅,最终翻译出:哦,这个路径其实对应的是硬盘上的第 1056、1057 和 998 块小地方,然后把数据取出来交给你。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5. 总结:操作系统是"大管家"
|
||||
## 4. 总结:伟大的幕后英雄
|
||||
|
||||
让我们用一个比喻总结操作系统的三大职责:
|
||||
让我们通过一个你每天都在经历的场景,串联起今天学到的知识。当你**双击鼠标打开一个游戏**时,为了伺候你,大管家做了什么?
|
||||
|
||||
| 职责 | 比喻 | 核心任务 |
|
||||
|------|------|---------|
|
||||
| **进程管理** | 餐厅排班员 | 安排谁先工作、工作多久 |
|
||||
| **内存管理** | 厨房管理员 | 分配工作台、防止冲突 |
|
||||
| **文件系统** | 仓库管理员 | 整理物资、快速查找 |
|
||||
1. **文件系统**:立刻从底层硬盘的杂乱数据块中,拼凑出游戏的执行文件和美术资产。
|
||||
2. **内存管理**:为你分配一个巨大的虚拟内存空间,制造出“这台电脑只有这一个游戏”的幻觉,并把刚才找到的文件放进物理内存的空隙里。
|
||||
3. **进程管理**:在它的名册上新建一个“游戏进程”,并在下一个瞬间,立刻剥夺其他正在运行软件的 CPU 权利,把 CPU 的计算力全盘移交给你的游戏。
|
||||
|
||||
::: tip 💡 核心启示
|
||||
**操作系统的本质是"资源管理"**。
|
||||
|
||||
- CPU 时间是资源 → 进程管理
|
||||
- 内存空间是资源 → 内存管理
|
||||
- 磁盘空间是资源 → 文件系统
|
||||
|
||||
理解了这一点,你就会明白:
|
||||
- 为什么电脑会变慢(进程太多、内存不足)
|
||||
- 为什么需要重启(清理资源、释放内存)
|
||||
- 为什么文件要整理(提高查找效率)
|
||||
:::
|
||||
我们之所以能那么轻松、优雅地在数字世界里冲浪,全都是因为底层的操作系统在替我们负重前行。
|
||||
|
||||
---
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- **操作系统原理**:深入学习进程调度、内存分页、文件系统实现
|
||||
- **Linux 系统编程**:学习如何与操作系统交互(系统调用)
|
||||
- **并发编程**:学习多进程、多线程编程
|
||||
- **系统监控**:学习使用 top、htop、vmstat 等工具监控系统状态
|
||||
如果你觉得操作系统的各种“管理学”十分有趣,你可以看看这些进阶话题:
|
||||
- **进程与线程的区别**:除了进程,还有一种叫作“线程”的东西,它们是干什么用的?(为什么 Google Chrome 那么吃内存?)
|
||||
- **页面置换算法**:当物理内存全都塞满了,但你又打开了一个新软件,操作系统该把谁的数据临时踢到硬盘里?(LRU 算法)
|
||||
- **操作系统的多态**:Windows 和 macOS 会在底层实现上有什么不同?为什么有些软件只能在特定系统上运行?
|
||||
|
||||
@@ -8,96 +8,73 @@
|
||||
|
||||
## 0. 全景图:从沙子到智能
|
||||
|
||||
现代计算机的"思考"能力,归根结底来自于一个简单的东西:**开关**。
|
||||
在探索计算机底层的过程中,常常会遇到一个最根本的问题:**现代计算机的“思考”能力,究竟从何而来?**
|
||||
|
||||
想象你有一个开关,可以控制灯的亮灭。现在,如果你有几十亿个这样的开关,并且能用它们组合出各种复杂的逻辑,会发生什么?这就是计算机的奥秘。
|
||||
如果剥开电脑闪亮的外壳,我们看到的通常只是一堆金属、塑料和硅晶片。它们本身没有生命,不懂数学,更不懂何为智能。但当电流穿过它们时,一切开始运转起来。归根结底,这一切都来自于一个再简单不过的物理抽象:**开关**。
|
||||
|
||||
**从沙子到智能的层次结构:**
|
||||
想象你面前有一个控制灯泡的开关。按下灯亮,表示为“1”;断开灯灭,表示为“0”。如果我们拥有几十亿个这样的开关,并且能够让**一个开关的输出去控制另一个开关**,从而组合出无比复杂的逻辑网络,会发生什么?
|
||||
|
||||
| 层级 | 名称 | 数量级 | 作用 | 类比 |
|
||||
| ----- | -------- | ------ | ---------------------------- | -------- |
|
||||
| **1** | 晶体管 | 数十亿 | 最基本的开关单元 | 一个开关 |
|
||||
| **2** | 逻辑门 | 数亿 | 实现基本逻辑运算 | 开关组合 |
|
||||
| **3** | 功能单元 | 数百 | 实现特定功能(加法、存储等) | 功能模块 |
|
||||
| **4** | CPU 核心 | 1-128 | 完整的处理器 | 大脑 |
|
||||
答案是一台能执行任意逻辑的通用计算平台。理解计算机系统的关键在于“抽象(Abstraction)”。就像搭积木一样,我们通过层叠的封装来控制底层的复杂度。以下是从沙子到智能的四个核心层级:
|
||||
|
||||
::: tip 逐行解读这张表
|
||||
**第1层(晶体管)**:这是最底层的"开关"。现代 CPU 使用的是 MOSFET(金属氧化物半导体场效应晶体管),它的特点是:给栅极加电压,源极和漏极之间就导通;不加电压,就断开。这就是"用电控制电"的开关。
|
||||
::: tip 逐层解构:从沙子到智能
|
||||
- **第一层:晶体管(数百亿级)**
|
||||
这是最底层的“开关”。现代 CPU 内部主要使用 MOSFET(金属氧化物半导体场效应晶体管)。给栅极施加电压,源极和漏极之间就导通。这就是“用电控制电”的物理起点,解决的核心问题是:**如何用电信号控制另一个电信号?**
|
||||
|
||||
**第2层(逻辑门)**:把晶体管组合起来,就能实现"与"、"或"、"非"等逻辑运算。比如 AND 门:两个输入都为 1 时输出才为 1。这就像两个串联的开关,必须都按下灯才会亮。
|
||||
- **第二层:逻辑门(数十亿级)**
|
||||
当我们把特定的晶体管串联或并联,奇妙的转换就发生了——电路变成了数学。例如 AND(与)门必须两个输入都是 1,输出才是 1;这构成了布尔代数在物理电路上的映射,解决的核心问题是:**如何把物理通断转化为基于 0 和 1 的逻辑运算?**
|
||||
|
||||
**第3层(功能单元)**:把逻辑门组合起来,就能实现更复杂的功能。加法器能做加法,寄存器能存储数据,多路选择器能选择数据。这些是 CPU 的"器官"。
|
||||
- **第三层:功能单元(数百级)**
|
||||
把基础的逻辑门拼装在一起,就能构建出有特定用途的计算模块。加法器处理算术运算,多路选择器控制数据流向,而寄存器赋予了电路记忆能力。解决的核心问题是:**如何构造出能够执行加法计算和记忆状态的机器?**
|
||||
|
||||
**第4层(CPU 核心)**:把功能单元组合起来,加上控制器、总线等,就形成了一个完整的 CPU 核心。它能取指令、解码、执行、写回结果——这就是"计算"的全部过程。
|
||||
- **第四层:CPU 核心(1-128核)**
|
||||
这是整个微架构的指挥中心。当你写下一行代码时,CPU 内部的各个部件正以每秒几十亿次的频率协同工作,执行着取指、解码、执行、写回的整个流程。解决的核心问题是:**如何让各模块协同一致,自动执行指定的程序序列?**
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. 晶体管:数字世界的开关
|
||||
|
||||
让我们从微观世界开始。下面这个组件展示了晶体管的基本原理,你可以试着操作一下,观察电流是如何流动的:
|
||||
|
||||
<TransistorDemo />
|
||||
|
||||
### 1.1 什么是晶体管?
|
||||
|
||||
::: tip 晶体管是什么?
|
||||
**晶体管(Transistor)** 是一种半导体器件,它可以像开关一样控制电流的通断。
|
||||
::: tip 概念引入
|
||||
在工程学中,**晶体管(Transistor)** 是一种改变了人类历史的半导体器件。在数字电路的语境下,我们可以直接把它抽象为一个完美的“开关”。
|
||||
|
||||
**生活类比**:想象一个水龙头:
|
||||
为什么我们需要晶体管?想想生活中的水龙头。你用手拧开阀门,水流就涌出。**晶体管其实就是一个纳米级的水龙头**:
|
||||
- **源极 (Source)** 和 **漏极 (Drain)** 就如同水管的两端。
|
||||
- **栅极 (Gate)** 就是那个用来控制水流的阀门。
|
||||
|
||||
- **水龙头**:你用手拧开关,控制水流
|
||||
- **晶体管**:用电压控制开关,控制电流
|
||||
|
||||
关键区别是:晶体管不是用手拧,而是用"电"来控制。这意味着一个开关可以控制另一个开关,从而实现"自动控制"。
|
||||
关键的区别在于:我们不是用手去拧开关,而是用**电压信号**。当一种开关能够被另一种开关产生的电信号所控制时,我们就跨过了从“人工干预”到“自动运算”的巨大鸿沟。
|
||||
:::
|
||||
|
||||
**晶体管的三个极:**
|
||||
|
||||
| 极 | 名称 | 作用 | 类比 |
|
||||
| ----------------- | -------- | -------------- | ---------- |
|
||||
| **源极 (Source)** | 电流入口 | 电流从这里进入 | 水管入口 |
|
||||
| **漏极 (Drain)** | 电流出口 | 电流从这里流出 | 水管出口 |
|
||||
| **栅极 (Gate)** | 控制端 | 控制是否导通 | 水龙头开关 |
|
||||
|
||||
### 1.2 晶体管如何表示 0 和 1?
|
||||
|
||||
计算机只认识 0 和 1,这和晶体管有什么关系?
|
||||
你可能会问:计算机所谓的“只认识 0 和 1”,在物理世界中究竟是什么样子?难道芯片里真的流淌着微小的 0 和 1 吗?
|
||||
|
||||
::: tip 用电压表示 0 和 1
|
||||
**核心思想**:用电压的高低来表示 0 和 1。
|
||||
当然不是。这一切全靠人为的**抽象约定**。我们要摒弃对连续模拟信号的执念,设定两个极端阈值:
|
||||
|
||||
- **高电压(如 3.3V)**:表示 1
|
||||
- **低电压(如 0V)**:表示 0
|
||||
- 我们把**高电压(比如 3.3V 或 1.0V)** 强行定义为逻辑的 **1**(True)。
|
||||
- 把**低电压(接近 0V)** 强行定义为逻辑的 **0**(False)。
|
||||
|
||||
这就像灯泡的亮和灭:
|
||||
这就是所谓的数字抽象能力:我们把充满噪音的模拟世界,硬生生地切分成了干净利落的 0 和 1。栅极输入高电压,晶体管导通,相当于开关合上;栅极输入低电压,开关断开。
|
||||
|
||||
- 灯亮 = 1
|
||||
- 灯灭 = 0
|
||||
### 1.3 晶体管数量的演进
|
||||
|
||||
晶体管的作用就是"控制灯泡的亮灭"——给栅极加高电压,源极和漏极导通,"灯泡"亮了(输出 1);给栅极低电压,源极和漏极断开,"灯泡"灭了(输出 0)。
|
||||
:::
|
||||
一个晶体管只能控制通断,显得极其微不足道。但如果把几十亿个这样的开关组合起来呢?观察下面这张体现摩尔定律的表格,了解一下现代芯片的发展。
|
||||
|
||||
### 1.3 从一个开关到几十亿
|
||||
| 时代标志 | 处理器芯片 | 晶体管数量 | 制程节点 | 时代意义 |
|
||||
| -------- | ---------------- | ---------- | -------- | ---------------------- |
|
||||
| 1971 | Intel 4004 | 2,300 | 10微米 | 微处理器黎明开端 |
|
||||
| 1993 | Intel Pentium | 310万 | 800纳米 | 个人电脑全面普及 |
|
||||
| 2006 | Intel Core 2 Duo | 2.91亿 | 65纳米 | 多核架构成为主流 |
|
||||
| 2020 | Apple M1 | 160亿 | 5纳米 | 移动端架构的反哺革命 |
|
||||
| 2023 | Apple M3 Max | 920亿 | 3纳米 | 接近原子的物理学极限 |
|
||||
|
||||
你可能好奇:一个开关能做什么?答案是:一个开关做不了什么,但几十亿个开关组合起来,就能做任何计算。
|
||||
|
||||
**现代 CPU 的晶体管数量:**
|
||||
|
||||
| 年份 | CPU | 晶体管数量 | 制程工艺 |
|
||||
| ---- | ------------- | ---------- | -------- |
|
||||
| 1971 | Intel 4004 | 2,300 | 10μm |
|
||||
| 1993 | Intel Pentium | 310万 | 0.8μm |
|
||||
| 2006 | Intel Core 2 | 2.91亿 | 65nm |
|
||||
| 2020 | Apple M1 | 160亿 | 5nm |
|
||||
| 2023 | Apple M3 Max | 920亿 | 3nm |
|
||||
|
||||
::: tip 什么是制程工艺?
|
||||
**制程工艺**(如 5nm、3nm)指的是晶体管的尺寸。数字越小,晶体管越小,同样面积能容纳的晶体管越多。
|
||||
|
||||
- **5nm**:大约是 50 个原子的宽度
|
||||
- **3nm**:大约是 30 个原子的宽度
|
||||
|
||||
制程越小,CPU 性能越强、功耗越低。但制造难度也指数级增加。
|
||||
:::
|
||||
> **深入思考:什么是 “3nm”?**
|
||||
> 当我们在新闻里听到 5nm、3nm 时,可以想象它有多微小。一个硅原子的直径大约是 0.2 纳米。所以在 3nm 的制程下,晶体管最关键的结构,只有几十个原子那么宽幅!这意味着我们是在量子力学规律生效的尺度边缘,来打造人类最庞大的算力堡垒。
|
||||
|
||||
---
|
||||
|
||||
@@ -105,184 +82,165 @@
|
||||
|
||||
### 2.1 从晶体管到逻辑门
|
||||
|
||||
一个晶体管只是一个开关,但把多个晶体管组合起来,就能实现"逻辑运算"。
|
||||
正如之前所说,单个晶体管只是对电流的简单控制。但当你把多个晶体管按照特定的结构排列时,物理学就变成了数学逻辑。在这个全新的维度上,我们不再谈论繁琐的电压和电流,而是直接谈论纯粹的逻辑“真”(1)与“假”(0)。
|
||||
|
||||
请通过下面的逻辑门演示,直观地感受一下开关组合的效果:
|
||||
|
||||
<LogicGateDemo />
|
||||
|
||||
### 2.2 基本逻辑门详解
|
||||
### 2.2 基本逻辑门介绍
|
||||
|
||||
**AND 门(与门)**:
|
||||
在我们的计算机体系结构中,有几种最基础的逻辑门,所有的超级计算机都是由这些积木搭建而成的:
|
||||
|
||||
- **规则**:两个输入都为 1,输出才为 1
|
||||
- **生活类比**:串联的两个开关,必须都按下灯才亮
|
||||
- **应用**:判断"多个条件是否同时满足"
|
||||
- **AND 门(与门)**:
|
||||
- **规则**:只有当所有输入都为 1 时,输出才为 1。
|
||||
- **直觉理解**:把两个晶体管**串联**。电流要想通过,必须同时打开两道关卡。如同开启银行金库,必须经理和主管同时插入各自的钥匙。
|
||||
|
||||
**OR 门(或门)**:
|
||||
- **OR 门(或门)**:
|
||||
- **规则**:只要有一个输入为 1,输出就为 1。
|
||||
- **直觉理解**:把两个晶体管**并联**。多条并行的通道,只要有一条路通了,电流就能流向彼岸。
|
||||
|
||||
- **规则**:任一个输入为 1,输出就为 1
|
||||
- **生活类比**:并联的两个开关,按任意一个灯就亮
|
||||
- **应用**:判断"是否满足任一条件"
|
||||
- **NOT 门(非门 / 反相器)**:
|
||||
- **规则**:输入 1 必定输出 0,输入 0 必定输出 1。
|
||||
- **直觉理解**:这是专门用来翻转状态的门,也是电路设计中经常用于信号整形的关键防线。
|
||||
|
||||
**NOT 门(非门)**:
|
||||
- **XOR 门(异或门)**:
|
||||
- **规则**:当两个输入**不相同**时,输出恰好为 1。
|
||||
- **直觉理解**:你可以把它理解为一个“侦测差异”的精密机器。这是我们在电路中执行二进制加法的杀手锏。
|
||||
|
||||
- **规则**:输入和输出相反
|
||||
- **生活类比**:反相器,开变关、关变开
|
||||
- **应用**:取反操作
|
||||
### 2.3 用逻辑门实现加法
|
||||
|
||||
**XOR 门(异或门)**:
|
||||
如果刚才介绍的逻辑门只能做简单的条件判断,那计算机到底是如何做数学运算的呢?
|
||||
|
||||
- **规则**:两个输入不同时输出 1
|
||||
- **生活类比**:判断"两个值是否不同"
|
||||
- **应用**:比较、加法运算
|
||||
我们先回想一下手算加法的方式:对应位相加,如果超出了限制(十进制是满十进一,二进制是满二进一),就向更高位“进位”。
|
||||
|
||||
### 2.3 用逻辑门做加法
|
||||
在二进制中,只有 0 和 1。对于一位数的加法,可能的情况只有四种:
|
||||
- `0 + 0 = 0` (本位是 0,不进位)
|
||||
- `0 + 1 = 1` (本位是 1,不进位)
|
||||
- `1 + 0 = 1` (本位是 1,不进位)
|
||||
- `1 + 1 = 10` (本位是 0,进位 1)
|
||||
|
||||
仔细观察这四种情况,你会发现:
|
||||
1. **本位的结果**,只有在两个输入**不同**时才为 1,这正是 **XOR 门(异或门)** 的逻辑。
|
||||
2. **进位的结果**,只有在两个输入**都为 1** 时才为 1,这正是 **AND 门(与门)** 的逻辑。
|
||||
|
||||
因此,只要把一个 XOR 门和一个 AND 门组合起来,我们就得到了能计算一位数加法的电路,这也是最基础的**半加器(Half Adder)**。
|
||||
|
||||
<AdderDemo />
|
||||
|
||||
::: tip 💡 加法器是怎么工作的?
|
||||
**半加器**:处理两个 1 位二进制数相加
|
||||
|
||||
- 输入:A、B(各 1 位)
|
||||
- 输出:和(S)、进位(C)
|
||||
- 公式:S = A XOR B,C = A AND B
|
||||
|
||||
**全加器**:处理两个 1 位二进制数相加,加上上一位的进位
|
||||
|
||||
- 输入:A、B、Cin(进位输入)
|
||||
- 输出:和(S)、Cout(进位输出)
|
||||
|
||||
**多位加法器**:把多个全加器级联起来
|
||||
|
||||
- 第 1 位加法器的进位输出,连接到第 2 位加法器的进位输入
|
||||
- 就像我们手算加法时"逢二进一"
|
||||
::: tip 核心解析:分解加法器
|
||||
为了处理真实世界中更复杂的数字,加法器需要像搭积木一样拼装:
|
||||
|
||||
1. **半加器(Half Adder)**:它可以处理两个一位数相加(即上述 XOR 和 AND 门的组合)。它计算了本位和进位,但没法接收来自更低位的进位。
|
||||
2. **全加器(Full Adder)**:在多位计算中,中间位数除了要把 A 和 B 加起来,还要处理来自低位的进位(Carry In)。把低位进位也加入逻辑后,就是全加器。
|
||||
3. **行波进位加法器(Ripple Carry Adder)**:要想处理 32 位或 64 位的数字,只需要把几十个全加器串联起来。进位信号便像波浪一样从低位一层层涌向高位,从而完成任意大小的加法。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能单元:逻辑门的组合
|
||||
|
||||
### 3.1 常见功能单元
|
||||
现在,手里握着逻辑门构成的积木,我们可以向更高的抽象层跃进了。单单计算加法是不够的,我们将成组的逻辑门打包,组装成具有特定功能的模块。这些模块我们统称为**功能单元(Functional Units)**。
|
||||
|
||||
| 单元 | 功能 | 组成 | 类比 |
|
||||
| -------------- | -------- | ---------------- | ---------------- |
|
||||
| **加法器** | 做加法 | 多个全加器级联 | 计算器的加法功能 |
|
||||
| **多路选择器** | 选择数据 | AND 门 + OR 门 | 多选一开关 |
|
||||
| **译码器** | 解码指令 | 多个 AND 门 | 翻译器 |
|
||||
| **寄存器** | 存储数据 | 触发器(锁存器) | 临时笔记本 |
|
||||
| **计数器** | 计数 | 触发器级联 | 计分牌 |
|
||||
### 3.1 常见功能模块分类
|
||||
|
||||
在设计 CPU 时,有一些经过时间考验的经典预制模块:
|
||||
|
||||
| 模块名称 | 承担的核心使命 | 内部的逻辑构造本质 | 现实生活中的绝佳隐喻 |
|
||||
| -------------- | ------------------------------------ | ------------------------------------ | -------------------- |
|
||||
| **加法器(Adder)** | 处理各种类型的算术运算引擎 | 海量全加器的高级按位级联 | 不知疲倦的算盘 |
|
||||
| **多路选择器(MUX)** | 控制数据的流向途径,实现多选一通道 | 巧妙融合 AND 门作为开关、OR 门进行汇总 | 铁路线上的精密道岔 |
|
||||
| **译码器(Decoder)** | 破解并翻译外部传入的二进制死指令 | 基于输入状态精确点亮特定输出的门阵列 | 破获密电的翻译员 |
|
||||
| **触发器(Flip-Flop)**| 突破电信号转瞬即逝的限制,记录历史 | 极其微妙的交叉反馈环路构成双稳态模式 | 会保持状态的跷跷板 |
|
||||
|
||||
为了直观地感受这些功能单元是如何工作的,你可以操作下面的组件,分别查看**多路选择器**和**译码器**的内部逻辑:
|
||||
|
||||
<FunctionalUnitDemo />
|
||||
|
||||
请通过下面这款组件实验,亲自窥探其中最令人着迷的部分——**记忆是如何凭空产生的**:
|
||||
|
||||
<RegisterDemo />
|
||||
|
||||
### 3.2 寄存器:存储 1 位数据
|
||||
### 3.2 寄存器:数据的存储单元
|
||||
|
||||
::: tip 💡 寄存器是怎么存储数据的?
|
||||
寄存器使用**触发器**电路来存储数据。触发器的特点是:一旦设置了状态,就能保持住,直到下一次改变。
|
||||
除了计算,计算机还需要能够长期或临时地记住数据。如果在运算过程中丧失了对前一秒的记忆,那任何复杂的计算都无法进行。计算机必须拥有某种手段保留过去的状态,这种能力主要仰仗于一种名为**触发器(Flip-Flop)**的电路结构。
|
||||
|
||||
**生活类比**:想象一个跷跷板:
|
||||
::: tip 深入理解:记忆本质上是一种循环
|
||||
大多数逻辑电路的信号流向都是向前的(前馈回路)。而要产生持续的“记忆”,早期的先驱们想到了一个绝妙的设计:将输出的电波重新反馈回输入端。
|
||||
|
||||
- 推一下左边,左边就沉下去,右边翘起来
|
||||
- 即使你松手,跷跷板也会保持这个状态
|
||||
- 只有再推一下,才会改变状态
|
||||
如同一个有着两个稳定静止点的精巧跷跷板结构。只要不受外界扰动,它凭借其闭环的设计,会永久性稳固在“左高右低(例如这就是记住了 0)”抑或是相反状态(记住了 1)。即便是转瞬即逝的状态改变,也能因闭环相互锁定而被长久“深锁”。
|
||||
|
||||
触发器就是这样的"电子跷跷板",能"记住"上一次被设置的状态。
|
||||
当我们将 32 个抑或 64 个这种触发器整齐地编排成一列,施加同一种强劲的时钟频率信号(Clock)来号令它们统一行动时,**寄存器(Register)**便应运而生了。它身居 CPU 系统的心脏位置,被当做极速的“工作草稿纸”,默默捍卫着你每一个即时的关键变量。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 4. CPU 架构:从功能单元到处理器
|
||||
|
||||
随着各种运算模块和记忆组件设计完毕,现在到了核心的综合阶段。如何将这些模块组合起来,让它们变成能自动执行指令的中央处理器(CPU)?
|
||||
|
||||
### 4.1 CPU 的核心组件
|
||||
|
||||
CPU 不是单一部件,而是多个功能单元协作工作:
|
||||
如果把 CPU 看作一个分工明确的机器,那么每个单元都有自己不可替代的位置:
|
||||
|
||||
| 组件 | 做什么 | 类比 |
|
||||
| ------------ | ------------------------------ | ------------------------ |
|
||||
| **控制器** | 取指、解码、发出控制信号 | 像指挥员,安排谁何时工作 |
|
||||
| **ALU** | 加减、与、或、比较等运算 | 像计算器,做算术与逻辑 |
|
||||
| **寄存器组** | 保存最常用的数据和中间结果 | 像桌面便签,比内存更快 |
|
||||
| **内部总线** | 在模块间传数据、地址、控制信息 | 像高速通道,把组件连成整体 |
|
||||
|
||||
一句话:控制器负责调度,ALU 负责计算,寄存器负责高速暂存,总线负责连接与传输。
|
||||
- **算术逻辑单元 (ALU)**:负责“干活”的运算单元,专门执行加减乘除和各种逻辑运算。
|
||||
- **寄存器组 (Register File)**:工作台上的临时抽屉,容量很小但速度极快,用于暂存当前正在计算的紧迫参数。
|
||||
- **内部总线 (Internal Bus)**:系统里的传送带,负责在各个模块之间搬运数据和信号。
|
||||
- **控制单元 (Control Unit)**:总指挥。它的使命就是从内存中读取用 0 和 1 组成的指令,解析出应该做什么,并向其他模块传达具体的控制信号,调度它们各司其职。
|
||||
|
||||
### 4.2 CPU 是如何执行指令的?
|
||||
|
||||
CPU 执行一条指令,需要经过四个阶段:
|
||||
不管写下的高级编程语言有多么复杂,最终都会变成内存中的一条条底层指令。CPU 执行任何指令的过程,本质上都在重复以下典型的四个步骤:
|
||||
|
||||
| 阶段 | 名称 | 做什么 | 类比 |
|
||||
| ----- | ----------------- | ---------------- | ------------------ |
|
||||
| **1** | 取指 (Fetch) | 从内存读取指令 | 从书架上取书 |
|
||||
| **2** | 解码 (Decode) | 分析指令要做什么 | 阅读书的内容 |
|
||||
| **3** | 执行 (Execute) | 执行运算 | 按书中的指示行动 |
|
||||
| **4** | 写回 (Write Back) | 把结果存回寄存器 | 把结果记在笔记本上 |
|
||||
1. **取指 (Fetch)**:循着当前程序执行的光标地址,探入相对漫长迟缓的缓存之中,把下一套二进制“指令”硬生生抓进核心。
|
||||
2. **译码 (Decode)**:指挥大脑马上分析:这道命令具体是要我移动内存,还是呼叫加法器拼凑运算?立刻将所需电路彻底连通唤醒。
|
||||
3. **执行 (Execute)**:指令派单到达诸如 ALU 等业务工厂车间,机器轰鸣,全力以赴进行硬核逻辑翻转。
|
||||
4. **写回 (Write Back)**:成果凝结时刻,将刚刚得手的答案慎重写至特定的寄存器或反馈回宽阔的内存。
|
||||
|
||||
::: tip 💡 指令周期
|
||||
这四个阶段组成一个**指令周期**。CPU 不断重复这个周期,一条一条执行指令,就实现了"计算"。
|
||||
点击下方的“时钟脉冲”,观察在这个死循环中,指令是如何一步步被拆解、执行,并涉及哪些硬件模块的:
|
||||
|
||||
现代 CPU 使用**流水线技术**,让多个指令的不同阶段并行执行:
|
||||
<CpuArchitectureDemo />
|
||||
|
||||
- 第 1 条指令在执行时
|
||||
- 第 2 条指令在解码
|
||||
- 第 3 条指令在取指
|
||||
::: tip 追求效率的极致:流水线(Pipeline)
|
||||
如果必须等上一条指令经历了这四个步骤后才开始下一条指令,效率显然太低。
|
||||
|
||||
这就像工厂流水线,大大提高了效率。
|
||||
就像工厂的流水线一样,芯片工程师引入了**指令流水线技术**。这意味着当第一部分电路在对指令 A 进行“执行”时,之前的电路并没有闲着,而是去对指令 B 进行“解码”,甚至是把指令 C 提前“取指”拿了出来。通过这种并行的重叠方式,CPU 的执行效率得到了极大的提升。
|
||||
:::
|
||||
|
||||
### 4.3 CPU 性能的关键指标
|
||||
|
||||
| 指标 | 含义 | 影响 | 典型值 |
|
||||
| ---------- | ---------------------- | ---------------------- | -------- |
|
||||
| **主频** | 每秒执行多少个时钟周期 | 主频越高,执行越快 | 3-5 GHz |
|
||||
| **核心数** | 独立的处理器数量 | 核心越多,并行能力越强 | 4-64 核 |
|
||||
| **缓存** | CPU 内部的高速存储 | 缓存越大,访问内存越少 | 8-64 MB |
|
||||
| **指令集** | CPU 能理解的指令集合 | 决定兼容性和功能 | x86、ARM |
|
||||
|
||||
---
|
||||
|
||||
## 5. 总结:从沙子到智能
|
||||
## 5. 总结:跨越抽象层级
|
||||
|
||||
每一层都是对下一层的抽象封装,从沙子到可运行软件的完整路径如下:
|
||||
回顾这一路,我们经历了计算机体系结构中最核心的层层抽象。这是将底层物理材料变为通用计算平台的完整路径:
|
||||
|
||||
1. **沙子(硅)** — 原材料:地球上最丰富的元素之一,提炼出高纯度硅。
|
||||
↓ 提纯 → 切割成晶圆
|
||||
1. **宏观物理:沙子(二氧化硅晶体)**
|
||||
→ *接受人类冶炼、切片、剧毒气体蚀刻等种种苛刻雕琢后*
|
||||
2. **微观物理:海量的晶体管开关** (以微电控微电)
|
||||
→ *经过工程大牛不眠不休的密集拉线,实现了惊人的数字抽象约束*
|
||||
3. **数字代数:AND / OR / NOT 逻辑门体系**
|
||||
→ *无情抹杀误差,以完美真值表衍生出基础行为*
|
||||
4. **微架构模块:功能单元积木集(加法器等组件)**
|
||||
→ *加入了系统生命节拍与记忆特性,进化为完整功能体*
|
||||
5. **复杂体系结构:庞大而精妙的 CPU 联合阵列**
|
||||
→ *面向全世界开发极客,彻底敞开了通往虚拟应用世界的大门*
|
||||
6. **万千应用王国:算法、系统级软件以及繁花似锦的互联网宇宙**
|
||||
|
||||
2. **硅晶圆** — 基底:直径约 30cm 的单晶硅片,表面极其光滑。
|
||||
↓ 光刻 → 蚀刻 → 掺杂
|
||||
计算机科学中最令人着迷的部分在于,**每一层封装都完美地隐藏了下一层的复杂细节**。作为一个软件开发者,当你写下 `salary = base + bonus` 时,完全不需要考虑底层电子的漂移以及半加器内电流的走向;同样,芯片硬件设计师也不需要操心这块芯片未来将运行什么软件。
|
||||
|
||||
3. **晶体管(开关)** — 数百亿个/芯片:Gate=1 导通,Gate=0 断开,用电压控制电流。
|
||||
↓ 组合成逻辑电路
|
||||
正是极端的层级解耦以及高度互不干扰的黑盒封装,合力孕育、铺就了现代科技的狂欢盛世。
|
||||
|
||||
4. **逻辑门** — 数十亿个:AND / OR / NOT / XOR,实现基本布尔运算。
|
||||
↓ 组合成功能模块
|
||||
::: tip 终极思考
|
||||
**归根究底,所谓的算力,不过是有限的密闭空间内海量开关重组的变幻;伴随着时钟的节拍,在这片小小的硅片上完成了复杂的运算。**
|
||||
|
||||
5. **功能单元** — 数百个:加法器、寄存器、多路选择器……各司其职。
|
||||
↓ 集成为完整处理器
|
||||
|
||||
6. **CPU 核心** — 1~128 核:ALU + 控制器 + 寄存器组,执行取指→解码→执行→写回。
|
||||
↓ 软件编程
|
||||
|
||||
7. **软件应用** — 无限可能:操作系统 / AI 模型 / 游戏 / 网页……一切皆指令。
|
||||
|
||||
计算机的本质是「开关的组合」:通过一层层抽象封装,最底层的物理材料最终变成能执行任意逻辑的通用计算平台。
|
||||
|
||||
::: tip 核心启示
|
||||
**计算机的本质是"开关的组合"**。
|
||||
|
||||
- 一个开关做不了什么
|
||||
- 但几十亿个开关,按特定方式组合,就能执行任何计算
|
||||
- 这就是"量变引起质变"的最好例证
|
||||
|
||||
理解这一点,你就会明白:
|
||||
|
||||
- 为什么计算机只认识 0 和 1
|
||||
- 为什么编程语言最终都要翻译成机器码
|
||||
- 为什么算法效率如此重要(因为每一步操作都需要大量晶体管参与)
|
||||
“量变最终引发质的飞跃”,这句话在计算机体系结构中被不断验证。当我们敲下键盘,注视着屏幕时,可以试着想象:在极其微小的硅基深处,此刻正有百亿级极小的晶体管,在电光火石之间拼尽全力进行着精密的协同。这或许就是最独特的计算机科学之美。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- **计算机组成原理**:深入了解 CPU、内存、I/O 的工作原理
|
||||
- **数字电路**:学习逻辑门、触发器、时序电路的设计
|
||||
- **计算机体系结构**:研究 CPU 的性能优化、流水线、缓存等
|
||||
- **汇编语言**:直接和 CPU 对话,理解指令执行过程
|
||||
如果你对底层技术充满好奇,可以尝试在以下几个方向继续探索:
|
||||
- **经典教材**:《计算机组成与设计(软硬件接口)》是深入学习体系结构的一本很好的参考书。
|
||||
- **数字逻辑仿真**:尝试使用逻辑仿真软件或基础元器件,动手搭建一个简单的 8 位加法器或模拟器。
|
||||
- **体系结构前沿**:了解多级缓存如何缓解“内存墙”问题、指令乱序执行的原理,以及 GPU 的特殊运算机制等。
|
||||
- **底层与汇编语言**:尝试学习一些基础汇编语言,理解高级语言最终是如何被转化为机器可以执行的十六进制指令的。
|
||||
|
||||
@@ -755,7 +755,7 @@ console.log('4. End')
|
||||
`setTimeout(0)`不保证立即执行,它至少会被延迟到当前调用栈清空、微任务队列清空之后。
|
||||
:::
|
||||
|
||||
<EventLoopDemo />
|
||||
<JSEventLoopDemo />
|
||||
|
||||
<MacroMicroTaskDemo />
|
||||
|
||||
|
||||
@@ -78,7 +78,46 @@ HTTP/1.1 200 OK
|
||||
|
||||
---
|
||||
|
||||
## 2. RESTful 设计:让 URL 会说话
|
||||
## 2. API 设计哲学:RPC / REST / GraphQL / gRPC
|
||||
|
||||
在开始具体的 RESTful 设计之前,先了解四种主流的 API 设计风格:
|
||||
|
||||
<ApiStyleCompare />
|
||||
|
||||
### 2.1 REST vs RESTful:有什么区别?
|
||||
|
||||
很多人会混淆这两个概念:
|
||||
|
||||
| 概念 | 含义 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **REST** | 一种架构风格 | 由 Roy Fielding 提出的设计理念,包含一组约束条件 |
|
||||
| **RESTful** | 符合 REST 风格的 | 形容词,表示 API 设计遵循了 REST 原则 |
|
||||
|
||||
**类比**:
|
||||
- REST 就像"极简主义"——一种设计理念
|
||||
- RESTful API 就像"极简风格的房间"——应用了这个理念的具体实现
|
||||
|
||||
**REST 的六大约束**:
|
||||
|
||||
| 约束 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **客户端-服务器分离** | 前后端独立开发,接口解耦 |
|
||||
| **无状态** | 每个请求包含所有必要信息,服务器不保存会话状态 |
|
||||
| **可缓存** | 响应应标明是否可缓存,提高性能 |
|
||||
| **统一接口** | 使用标准的 HTTP 方法和状态码 |
|
||||
| **分层系统** | 客户端无需知道连接的是哪层服务器 |
|
||||
| **按需代码**(可选) | 服务器可以扩展客户端功能 |
|
||||
|
||||
::: tip 💡 为什么 REST 最常用?
|
||||
1. **学习成本低**:HTTP 协议本身就体现了 REST 思想
|
||||
2. **生态成熟**:工具、框架、文档丰富
|
||||
3. **通用性强**:任何语言、任何平台都能调用
|
||||
4. **易于缓存**:GET 请求天然可缓存,CDN 友好
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. RESTful 设计:让 URL 会说话
|
||||
|
||||
**REST**(Representational State Transfer)是一种架构风格,核心思想是:
|
||||
|
||||
@@ -86,7 +125,7 @@ HTTP/1.1 200 OK
|
||||
- 用 URL 标识资源
|
||||
- 用 HTTP 方法操作资源
|
||||
|
||||
### 2.1 用仓库来类比
|
||||
### 3.1 用仓库来类比
|
||||
|
||||
| 仓库概念 | REST 对应 | 示例 |
|
||||
| :--- | :--- | :--- |
|
||||
@@ -96,21 +135,21 @@ HTTP/1.1 200 OK
|
||||
|
||||
**关键原则**:URL 是名词,不是动词。
|
||||
|
||||
### 2.2 URL 设计规则
|
||||
### 3.2 URL 设计规则
|
||||
|
||||
| 规则 | 错误示例 | 正确示例 | 说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **用名词不用动词** | `GET /getUsers`<br>`POST /createOrder` | `GET /users`<br>`POST /orders` | URL 是资源地址,HTTP 方法已表达操作 |
|
||||
| **用复数形式** | `GET /user`<br>`GET /order` | `GET /users`<br>`GET /users/123` | 统一用复数,避免 `/user` 和 `/users` 混用 |
|
||||
| **小写+连字符** | `GET /UserProfiles`<br>`GET /user_profiles` | `GET /user-profiles`<br>`GET /order-items` | URL 大小写敏感,统一小写最安全 |
|
||||
| **避免层级过深** | `GET /users/123/orders/456/items/789` | `GET /users/123/orders`<br>`GET /order-items/789` | 超过 3 层考虑重构,用扁平化路径 |
|
||||
| **过滤用查询参数** | `GET /products/category/phone/price/5000` | `GET /products?category=phone&price_max=5000` | 过滤、排序、分页都用查询参数 |
|
||||
| 用名词不用动词 | `/getUsers` | `/users` | URL 表示资源,HTTP 方法表示操作 |
|
||||
| 用复数形式 | `/user` | `/users` | 统一复数风格 |
|
||||
| 小写+连字符 | `/UserProfiles` | `/user-profiles` | URL 大小写敏感 |
|
||||
| 避免层级过深 | `/a/b/c/d/e` | `/a/b/c` | 最多 3 层 |
|
||||
| 过滤用查询参数 | `/products/phone/5000` | `/products?cat=phone` | 过滤条件用 `?` 参数 |
|
||||
|
||||
::: tip 💡 URL 大小写敏感
|
||||
统一用小写 + 连字符(-)是最安全的做法,避免大小写混乱和下划线风格不一致的问题。
|
||||
:::
|
||||
|
||||
### 2.3 HTTP 方法选择
|
||||
### 3.3 HTTP 方法选择
|
||||
|
||||
| 方法 | 用途 | 幂等性 | 安全性 | 典型场景 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
@@ -131,11 +170,11 @@ HTTP/1.1 200 OK
|
||||
|
||||
---
|
||||
|
||||
## 3. 状态码:让错误"会说话"
|
||||
## 4. 状态码:让错误"会说话"
|
||||
|
||||
HTTP 状态码是服务器告诉客户端"发生了什么"的标准方式。
|
||||
|
||||
### 3.1 状态码分类
|
||||
### 4.1 状态码分类
|
||||
|
||||
| 分类 | 含义 | 典型状态码 |
|
||||
| :--- | :--- | :--- |
|
||||
@@ -144,7 +183,7 @@ HTTP 状态码是服务器告诉客户端"发生了什么"的标准方式。
|
||||
| **4xx** | 客户端错误 | 400 参数错误、401 未认证、404 不存在 |
|
||||
| **5xx** | 服务端错误 | 500 内部错误、503 服务不可用 |
|
||||
|
||||
### 3.2 常用状态码演示
|
||||
### 4.2 常用状态码演示
|
||||
|
||||
👇 **动手试试看**:点击下方按钮,了解常见状态码的含义:
|
||||
|
||||
@@ -152,7 +191,7 @@ HTTP 状态码是服务器告诉客户端"发生了什么"的标准方式。
|
||||
|
||||
---
|
||||
|
||||
## 4. 错误处理:优雅地"拒绝"
|
||||
## 5. 错误处理:优雅地"拒绝"
|
||||
|
||||
好的错误处理能让客户端"看状态码就知道怎么回事",而不是去猜。
|
||||
|
||||
@@ -188,7 +227,7 @@ HTTP/1.1 500 Internal Server Error
|
||||
|
||||
危险:暴露了代码结构、数据库查询,攻击者可以利用这些信息。
|
||||
|
||||
### 4.2 正确的错误处理演示
|
||||
### 5.2 正确的错误处理演示
|
||||
|
||||
👇 **动手试试看**:对比"好的"和"差的"错误响应设计:
|
||||
|
||||
@@ -196,9 +235,9 @@ HTTP/1.1 500 Internal Server Error
|
||||
|
||||
---
|
||||
|
||||
## 5. 版本控制:API 的"向后兼容"
|
||||
## 6. 版本控制:API 的"向后兼容"
|
||||
|
||||
### 5.1 为什么要版本控制?
|
||||
### 6.1 为什么要版本控制?
|
||||
|
||||
场景:你的 App 有 100 万用户,需要修改订单接口。
|
||||
|
||||
@@ -210,7 +249,7 @@ HTTP/1.1 500 Internal Server Error
|
||||
- `/v1/orders` - 旧接口,继续服务旧 App
|
||||
- `/v2/orders` - 新接口,新功能在这里
|
||||
|
||||
### 5.2 版本控制策略
|
||||
### 6.2 版本控制策略
|
||||
|
||||
| 策略 | 示例 | 优点 | 缺点 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
@@ -218,66 +257,251 @@ HTTP/1.1 500 Internal Server Error
|
||||
| **请求头** | `Accept: vnd.api.v2+json` | URL 干净 | 不便调试 |
|
||||
| **查询参数** | `/users?version=2` | 简单 | 不够标准 |
|
||||
|
||||
### 5.3 版本控制演示
|
||||
### 6.3 版本演进示例
|
||||
|
||||
👇 **动手试试看**:了解 API 版本控制的策略和最佳实践:
|
||||
以用户接口为例,展示 v1 到 v2 的演进:
|
||||
|
||||
<ApiVersioningDemo />
|
||||
| 接口 | v1(旧版) | v2(新版) | 变化说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **获取用户** | `GET /v1/users`<br>返回:`name, email` | `GET /v2/users`<br>返回:`name, email, avatar, phone` | 新增头像、手机号字段 |
|
||||
| **创建订单** | `POST /v1/orders`<br>接收:`items[]` | `POST /v2/orders`<br>接收:`items[], coupons[]` | 新增优惠券支持 |
|
||||
| **批量操作** | 无 | `POST /v2/orders/batch` | 新增批量创建接口 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 响应结构:标准化的"数据契约"
|
||||
|
||||
无论成功还是失败,响应结构应该保持一致:
|
||||
|
||||
### 6.1 标准响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": { ... },
|
||||
"request_id": "req-550e8400",
|
||||
"timestamp": "2024-01-15T09:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `code` | number | 业务状态码,0 表示成功 |
|
||||
| `message` | string | 状态描述 |
|
||||
| `data` | any | 业务数据 |
|
||||
| `request_id` | string | 请求唯一标识,用于问题追踪 |
|
||||
| `timestamp` | string | 响应时间戳 |
|
||||
|
||||
### 6.2 分页响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total": 156,
|
||||
"total_pages": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip 💡 为什么要 request_id?
|
||||
**request_id** 是问题追踪的关键:
|
||||
|
||||
1. 用户反馈:"支付失败,错误 ID 是 abc123"
|
||||
2. 技术人员直接在日志里搜索 abc123,立即定位问题
|
||||
3. 分布式系统中,每个服务都记录相同的 request_id,可以把所有相关日志聚合起来
|
||||
::: tip 💡 版本控制最佳实践
|
||||
- **保持向后兼容**:v1 接口至少维护 6-12 个月,给客户端升级时间
|
||||
- **文档同步更新**:每个版本有独立的 API 文档
|
||||
- **废弃公告**:提前通知 v1 将在何时下线,引导迁移
|
||||
- **监控使用情况**:统计 v1 调用量,确认可以安全下线后再停止服务
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 7. 实战:电商系统 API 设计示例
|
||||
## 7. 响应结构设计
|
||||
|
||||
响应结构是前后端协作的"数据契约",统一格式能大幅降低沟通成本。
|
||||
|
||||
<ResponseStructureDemo />
|
||||
|
||||
### 7.1 大厂实践参考
|
||||
|
||||
::: details Google API 设计指南
|
||||
参考 [Google API Design Guide](https://cloud.google.com/apis/design/errors),Google 要求所有 API 错误响应必须包含 `google.rpc.Status` 消息结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": 429,
|
||||
"message": "资源不足,请稍后重试",
|
||||
"status": "RESOURCE_EXHAUSTED",
|
||||
"details": [
|
||||
{
|
||||
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
|
||||
"reason": "RESOURCE_AVAILABILITY",
|
||||
"domain": "compute.googleapis.com",
|
||||
"metadata": {
|
||||
"zone": "us-east1-a",
|
||||
"service": "compute"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**核心要求**:
|
||||
- 必须包含 `ErrorInfo` 提供机器可读的错误标识
|
||||
- `message` 面向开发者,用简洁语言描述问题和解决方案
|
||||
- `details` 数组可包含 `LocalizedMessage`(本地化消息)、`Help`(帮助链接)等
|
||||
:::
|
||||
|
||||
::: details Microsoft REST API 指南
|
||||
参考 [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md),微软强调响应的一致性:
|
||||
|
||||
**错误与故障的分类**:
|
||||
- **错误(Error)**:客户端传递无效数据导致,返回 4xx,不影响 API 可用性
|
||||
- **故障(Fault)**:服务端无法正确响应有效请求,返回 5xx,影响 API 可用性
|
||||
|
||||
**响应标头规范**:
|
||||
- `Date`:必须返回,使用 RFC 5322 格式(GMT 时区)
|
||||
- `Content-Type`:必须返回
|
||||
- `ETag`:支持乐观并发控制的资源必须返回
|
||||
:::
|
||||
|
||||
::: details 阿里巴巴 Java 开发手册
|
||||
参考 [阿里巴巴 Java 开发手册](https://developer.aliyun.com/special/tech-java),阿里对 API 响应有以下规范:
|
||||
|
||||
**统一返回对象**:
|
||||
```java
|
||||
public class Result<T> {
|
||||
private Integer code;
|
||||
private String message;
|
||||
private T data;
|
||||
private String requestId;
|
||||
}
|
||||
```
|
||||
|
||||
**错误码分段设计**:
|
||||
| 范围 | 类型 | 示例 |
|
||||
| :--- | :--- | :--- |
|
||||
| 0 | 成功 | 0 |
|
||||
| 1xxxx | 参数错误 | 10001 缺少必填参数 |
|
||||
| 2xxxx | 业务错误 | 20001 余额不足 |
|
||||
| 3xxxx | 认证错误 | 30001 未登录 |
|
||||
| 5xxxx | 系统错误 | 50001 数据库异常 |
|
||||
:::
|
||||
|
||||
::: details Stripe API 响应设计
|
||||
参考 [Stripe API Documentation](https://docs.stripe.com/api/errors),Stripe 的错误响应设计非常精细:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"type": "card_error",
|
||||
"code": "card_declined",
|
||||
"message": "Your card was declined.",
|
||||
"param": "number",
|
||||
"decline_code": "insufficient_funds",
|
||||
"doc_url": "https://stripe.com/docs/error-codes/card-declined"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- `type` 区分错误类型:`api_error`、`card_error`、`invalid_request_error`
|
||||
- `param` 指出具体哪个参数出错,前端可直接定位表单字段
|
||||
- `doc_url` 提供文档链接,开发者可深入了解
|
||||
- `decline_code` 提供更细粒度的错误原因
|
||||
:::
|
||||
|
||||
::: details JSON:API 规范
|
||||
参考 [JSON:API Specification](https://jsonapi.org/format/),这是一个业界广泛采纳的 JSON API 响应规范:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "articles",
|
||||
"id": "1",
|
||||
"attributes": {
|
||||
"title": "JSON:API 规范详解"
|
||||
},
|
||||
"relationships": {
|
||||
"author": {
|
||||
"data": { "type": "users", "id": "9" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"included": [
|
||||
{
|
||||
"type": "users",
|
||||
"id": "9",
|
||||
"attributes": {
|
||||
"name": "张三"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**核心设计**:
|
||||
- `data` 包含主资源,必须有 `type` 和 `id`
|
||||
- `attributes` 存放资源属性
|
||||
- `relationships` 描述资源关联
|
||||
- `included` 避免重复请求,一次性返回关联数据
|
||||
:::
|
||||
|
||||
::: details GitHub REST API 响应设计
|
||||
参考 [GitHub REST API Documentation](https://docs.github.com/en/rest),GitHub 的响应设计注重开发者体验:
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"id": 1296269,
|
||||
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
|
||||
"name": "Hello-World",
|
||||
"full_name": "octocat/Hello-World",
|
||||
"owner": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif"
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/octocat/Hello-World"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"message": "Bad credentials",
|
||||
"documentation_url": "https://docs.github.com/rest"
|
||||
}
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- 响应包含多种 URL 格式(`html_url`、`url`)方便不同场景使用
|
||||
- 错误响应包含 `documentation_url` 指向文档
|
||||
- 使用 `Link` 响应头实现分页导航
|
||||
:::
|
||||
|
||||
::: details Twitter/X API v2 响应设计
|
||||
参考 [Twitter API v2 Documentation](https://developer.twitter.com/en/docs/twitter-api),Twitter API v2 采用简洁的响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "1460323737035677698",
|
||||
"text": "Hello, Twitter!"
|
||||
},
|
||||
"includes": {
|
||||
"users": [
|
||||
{
|
||||
"id": "2244994945",
|
||||
"name": "Twitter Dev",
|
||||
"username": "TwitterDev"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**设计亮点**:
|
||||
- `data` 包含主数据,`includes` 包含关联数据(类似 JSON:API)
|
||||
- 支持字段选择:`?tweet.fields=created_at,public_metrics`
|
||||
- 分页使用 `next_token` 和 `previous_token`
|
||||
:::
|
||||
|
||||
### 7.2 最佳实践总结
|
||||
|
||||
综合以上规范,响应结构设计应遵循以下原则:
|
||||
|
||||
1. **一致性优先**:所有接口使用相同的响应结构,前端可统一封装请求层
|
||||
2. **机器可读**:错误码 + 错误原因(reason)让程序能自动处理
|
||||
3. **人类友好**:message 描述清晰,包含解决建议
|
||||
4. **可追踪**:request_id 贯穿请求全链路,便于问题定位
|
||||
5. **国际化支持**:通过 details 扩展本地化消息
|
||||
|
||||
### 7.3 data 字段设计规范
|
||||
|
||||
`data` 是响应的核心,其设计直接影响前端开发效率。
|
||||
|
||||
<DataFieldDesignDemo />
|
||||
|
||||
### 7.4 错误响应设计进阶
|
||||
|
||||
<ErrorResponseDesignDemo />
|
||||
|
||||
::: tip 参考链接
|
||||
- [Google API Design Guide - Errors](https://cloud.google.com/apis/design/errors)
|
||||
- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines)
|
||||
- [阿里巴巴 Java 开发手册](https://developer.aliyun.com/special/tech-java)
|
||||
- [Heroku HTTP API Design Guide](https://github.com/interagent/http-api-design)
|
||||
- [Stripe API - Errors](https://docs.stripe.com/api/errors)
|
||||
- [JSON:API Specification](https://jsonapi.org/format/)
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 8. 实战:电商系统 API 设计示例
|
||||
|
||||
```
|
||||
# 用户模块
|
||||
@@ -300,6 +524,100 @@ GET /v1/products?category=phone&price_max=5000&sort=price_desc&page=1
|
||||
|
||||
---
|
||||
|
||||
## 9. 用 AI 辅助设计 API
|
||||
|
||||
AI 可以帮助你快速生成符合规范的 API 设计。关键在于提供清晰的上下文和约束条件。
|
||||
|
||||
### 9.1 提示词模板
|
||||
|
||||
```
|
||||
你是一位资深的后端架构师,精通 RESTful API 设计。请帮我设计一套 API 接口。
|
||||
|
||||
## 业务背景
|
||||
[描述你的业务场景,例如:电商系统、博客平台、任务管理等]
|
||||
|
||||
## 功能需求
|
||||
[列出需要的功能模块,例如:
|
||||
- 用户管理:注册、登录、个人信息
|
||||
- 订单管理:创建订单、查询订单、取消订单
|
||||
- 商品管理:商品列表、商品详情、搜索]
|
||||
|
||||
## 设计要求
|
||||
1. 遵循 RESTful 规范
|
||||
2. URL 使用名词复数,小写+连字符
|
||||
3. 正确使用 HTTP 方法(GET/POST/PUT/PATCH/DELETE)
|
||||
4. 统一的响应格式:{ code, message, data, request_id }
|
||||
5. 合理的状态码使用
|
||||
6. 版本控制:URL 路径方式(/v1/)
|
||||
|
||||
## 输出格式
|
||||
请按以下格式输出:
|
||||
|
||||
### 接口列表
|
||||
| 方法 | URL | 描述 | 请求体 | 响应体 |
|
||||
|------|-----|------|--------|--------|
|
||||
|
||||
### 请求/响应示例
|
||||
[关键接口的详细示例]
|
||||
|
||||
### 状态码说明
|
||||
[使用的状态码及其含义]
|
||||
```
|
||||
|
||||
### 9.2 实战示例:电商订单 API
|
||||
|
||||
**输入提示词:**
|
||||
|
||||
```
|
||||
你是一位资深的后端架构师,精通 RESTful API 设计。请帮我设计一套电商订单系统的 API 接口。
|
||||
|
||||
## 业务背景
|
||||
一个 B2C 电商平台,用户可以浏览商品、下单购买、查看订单状态。
|
||||
|
||||
## 功能需求
|
||||
- 订单模块:创建订单、查询订单列表、查询订单详情、取消订单、支付订单
|
||||
- 购物车模块:添加商品、修改数量、删除商品、查看购物车
|
||||
|
||||
## 设计要求
|
||||
1. 遵循 RESTful 规范
|
||||
2. URL 使用名词复数,小写+连字符
|
||||
3. 正确使用 HTTP 方法
|
||||
4. 统一的响应格式
|
||||
5. 版本控制:/v1/
|
||||
```
|
||||
|
||||
**AI 输出示例:**
|
||||
|
||||
| 方法 | URL | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `POST` | `/v1/orders` | 创建订单 |
|
||||
| `GET` | `/v1/orders` | 查询订单列表 |
|
||||
| `GET` | `/v1/orders/{id}` | 查询订单详情 |
|
||||
| `PATCH` | `/v1/orders/{id}/status` | 更新订单状态(取消/支付) |
|
||||
| `GET` | `/v1/users/{id}/cart` | 获取购物车 |
|
||||
| `POST` | `/v1/users/{id}/cart/items` | 添加商品到购物车 |
|
||||
| `PATCH` | `/v1/users/{id}/cart/items/{itemId}` | 修改购物车商品数量 |
|
||||
| `DELETE` | `/v1/users/{id}/cart/items/{itemId}` | 删除购物车商品 |
|
||||
|
||||
### 9.3 AI 辅助设计的注意事项
|
||||
|
||||
| 注意点 | 说明 |
|
||||
| :--- | :--- |
|
||||
| **提供完整上下文** | 业务背景、用户角色、数据关系都要说清楚 |
|
||||
| **明确约束条件** | 命名规范、版本策略、响应格式等要提前定义 |
|
||||
| **迭代优化** | 第一次输出可能不完美,追问细节、要求修改 |
|
||||
| **人工审核** | AI 生成的内容需要人工检查是否符合业务需求 |
|
||||
| **补充边界情况** | 让 AI 考虑错误处理、权限控制、分页等边界情况 |
|
||||
|
||||
::: tip 💡 追问技巧
|
||||
- "请补充每个接口的错误响应示例"
|
||||
- "请考虑分页、排序、过滤参数"
|
||||
- "请添加接口的权限控制说明"
|
||||
- "请检查是否符合 RESTful 最佳实践"
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 名词速查表
|
||||
|
||||
| 名词 | 英文 | 解释 |
|
||||
|
||||
Reference in New Issue
Block a user