Files
sanbuphy 3af119a598 feat(appendix): 添加多个交互式演示组件,完善 AI/Infra 等章节内容
- 新增 Vibe Coding 全栈相关演示组件 (DeveloperSkillShift, FrontendTriad, BackendCore 等)
- 新增 RAG 相关组件 (RAGPipeline, ChunkingStrategy, Retrieval 等)
- 新增 Embedding & Vector 相关组件 (EmbeddingConcept, VectorSimilarity 等)
- 新增 AI Native App 设计组件 (AINativeArch, PromptDesign 等)
- 新增 Infrastructure as Code 组件 (IaCConcept, TerraformWorkflow 等)
- 新增 DNS & HTTPS 演示组件 (DnsResolution, HttpsHandshake 等)
- 新增 Model Finetuning 组件 (FinetuningPipeline 等)
- 更新多个章节的 markdown 内容,集成交互式演示
2026-02-24 18:22:58 +08:00

375 lines
10 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="iac-tool-comparison-demo">
<div class="demo-label">交互演示 主流 IaC 工具对比</div>
<div class="tool-selector">
<span class="selector-hint">选择要对比的工具至少选 2 </span>
<div class="tool-chips">
<button
v-for="tool in tools"
:key="tool.name"
:class="['tool-chip', { selected: selectedTools.includes(tool.name) }]"
:style="selectedTools.includes(tool.name) ? { background: tool.color, borderColor: tool.color, color: '#fff' } : {}"
@click="toggleTool(tool.name)"
>
{{ tool.icon }} {{ tool.name }}
</button>
</div>
</div>
<div v-if="selectedTools.length >= 2" class="comparison-grid">
<table>
<thead>
<tr>
<th class="feature-col">特性</th>
<th v-for="name in selectedTools" :key="name" class="tool-col">
<span class="tool-header-icon">{{ getToolByName(name).icon }}</span>
<span>{{ name }}</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="feature in features" :key="feature.key">
<td class="feature-cell">{{ feature.label }}</td>
<td v-for="name in selectedTools" :key="name" class="value-cell">
<span :class="getCellClass(name, feature.key)">
{{ getToolByName(name).features[feature.key] }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div v-else class="empty-hint">
请至少选择 2 个工具进行对比
</div>
<Transition name="fade">
<div v-if="selectedDetail" class="detail-card">
<div class="detail-header">
<span class="detail-icon">{{ selectedDetail.icon }}</span>
<span class="detail-name">{{ selectedDetail.name }}</span>
<button class="close-btn" @click="detailName = ''"></button>
</div>
<p class="detail-desc">{{ selectedDetail.desc }}</p>
<div class="detail-code">
<div class="code-label">示例代码片段</div>
<pre class="code-block"><code>{{ selectedDetail.example }}</code></pre>
</div>
</div>
</Transition>
<div class="detail-hint" v-if="selectedTools.length >= 2 && !detailName">
点击下方工具名称查看详细介绍和代码示例
</div>
<div class="tool-detail-btns" v-if="selectedTools.length >= 2">
<button
v-for="name in selectedTools"
:key="name"
:class="['detail-btn', { active: detailName === name }]"
@click="detailName = detailName === name ? '' : name"
>
{{ getToolByName(name).icon }} {{ name }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const selectedTools = ref(['Terraform', 'CloudFormation'])
const detailName = ref('')
const selectedDetail = computed(() => {
if (!detailName.value) return null
return tools.find(t => t.name === detailName.value)
})
const features = [
{ key: 'vendor', label: '厂商' },
{ key: 'language', label: '配置语言' },
{ key: 'style', label: '声明式/命令式' },
{ key: 'multiCloud', label: '多云支持' },
{ key: 'stateManagement', label: '状态管理' },
{ key: 'learning', label: '学习曲线' },
{ key: 'community', label: '社区生态' },
{ key: 'bestFor', label: '最佳场景' }
]
const tools = [
{
name: 'Terraform',
icon: '🟣',
color: '#7c3aed',
features: {
vendor: 'HashiCorp',
language: 'HCL',
style: '声明式',
multiCloud: '原生多云',
stateManagement: 'State 文件',
learning: '中等',
community: '非常活跃',
bestFor: '多云/混合云'
},
desc: 'Terraform 是目前最流行的开源 IaC 工具,由 HashiCorp 开发。它使用自研的 HCL 语言,通过 Provider 机制支持几乎所有主流云平台。',
example: `resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
tags = { Env = "prod" }
}
resource "aws_instance" "web" {
ami = "ami-0c55b159"
instance_type = "t3.micro"
}`
},
{
name: 'CloudFormation',
icon: '🟠',
color: '#ea580c',
features: {
vendor: 'AWS',
language: 'YAML / JSON',
style: '声明式',
multiCloud: '仅 AWS',
stateManagement: 'AWS 托管',
learning: '中等偏高',
community: 'AWS 生态',
bestFor: '纯 AWS 环境'
},
desc: 'CloudFormation 是 AWS 原生的 IaC 服务,与 AWS 服务深度集成。状态由 AWS 自动管理,无需额外维护 State 文件。',
example: `Resources:
WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0c55b159
InstanceType: t3.micro
Tags:
- Key: Name
Value: web-server`
},
{
name: 'Pulumi',
icon: '🔵',
color: '#2563eb',
features: {
vendor: 'Pulumi',
language: 'TypeScript/Python/Go',
style: '命令式 + 声明式',
multiCloud: '原生多云',
stateManagement: 'Pulumi Cloud / 自管',
learning: '低(熟悉编程语言)',
community: '快速增长',
bestFor: '开发者友好场景'
},
desc: 'Pulumi 允许使用真正的编程语言(TypeScript、Python、Go 等)来定义基础设施,对开发者非常友好,支持条件判断、循环等编程特性。',
example: `import * as aws from "@pulumi/aws"
const bucket = new aws.s3.Bucket("data", {
tags: { Env: "prod" }
})
const server = new aws.ec2.Instance("web", {
ami: "ami-0c55b159",
instanceType: "t3.micro",
})`
},
{
name: 'Ansible',
icon: '🔴',
color: '#dc2626',
features: {
vendor: 'Red Hat',
language: 'YAML (Playbook)',
style: '命令式',
multiCloud: '通过模块支持',
stateManagement: '无状态(幂等)',
learning: '低',
community: '非常活跃',
bestFor: '配置管理 + 编排'
},
desc: 'Ansible 是一个无代理的自动化工具,擅长配置管理和应用部署。它通过 SSH 连接目标机器执行任务,无需安装客户端。',
example: `- name: 部署 Web 服务器
hosts: webservers
tasks:
- name: 安装 Nginx
apt:
name: nginx
state: present
- name: 启动服务
service:
name: nginx
state: started`
}
]
function getToolByName(name) {
return tools.find(t => t.name === name)
}
function toggleTool(name) {
const idx = selectedTools.value.indexOf(name)
if (idx >= 0) {
if (selectedTools.value.length > 2) {
selectedTools.value.splice(idx, 1)
}
} else {
selectedTools.value.push(name)
}
}
function getCellClass(toolName, featureKey) {
const val = getToolByName(toolName).features[featureKey]
if (featureKey === 'multiCloud') {
if (val.includes('原生多云')) return 'cell-good'
if (val.includes('仅')) return 'cell-warn'
return ''
}
if (featureKey === 'learning') {
if (val === '低') return 'cell-good'
if (val.includes('高')) return 'cell-warn'
return ''
}
return ''
}
</script>
<style scoped>
.iac-tool-comparison-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: 1rem;
text-align: center;
}
.selector-hint {
font-size: 0.78rem;
color: var(--vp-c-text-3);
display: block;
margin-bottom: 0.5rem;
}
.tool-chips {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
margin-bottom: 1rem;
}
.tool-chip {
padding: 5px 14px;
border: 1px solid var(--vp-c-divider);
border-radius: 20px;
background: var(--vp-c-bg);
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.tool-chip:hover { transform: scale(1.05); }
.comparison-grid { overflow-x: auto; margin-bottom: 1rem; }
.comparison-grid table {
width: 100%;
border-collapse: collapse;
font-size: 0.78rem;
}
.comparison-grid th,
.comparison-grid td {
padding: 8px 10px;
border: 1px solid var(--vp-c-divider);
text-align: center;
}
.comparison-grid th {
background: var(--vp-c-bg-alt);
font-weight: 600;
}
.feature-col { text-align: left; min-width: 80px; }
.feature-cell { font-weight: 600; text-align: left; }
.tool-header-icon { margin-right: 4px; }
.cell-good { color: #10b981; font-weight: 600; }
.cell-warn { color: #f59e0b; }
.empty-hint {
text-align: center;
padding: 2rem;
color: var(--vp-c-text-3);
font-size: 0.85rem;
}
.detail-hint {
text-align: center;
font-size: 0.75rem;
color: var(--vp-c-text-3);
margin-bottom: 0.5rem;
}
.tool-detail-btns {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.detail-btn {
padding: 4px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
cursor: pointer;
font-size: 0.78rem;
transition: all 0.2s;
}
.detail-btn.active {
background: var(--vp-c-brand);
color: #fff;
border-color: var(--vp-c-brand);
}
.detail-card {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg);
margin-top: 0.5rem;
}
.detail-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 0.5rem;
}
.detail-icon { font-size: 1.2rem; }
.detail-name { font-weight: 600; font-size: 1rem; flex: 1; }
.close-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
color: var(--vp-c-text-3);
}
.detail-desc {
font-size: 0.82rem;
color: var(--vp-c-text-2);
line-height: 1.6;
margin-bottom: 0.8rem;
}
.code-label {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-bottom: 4px;
}
.code-block {
background: #1a1a2e;
color: #e0e0e0;
padding: 0.8rem;
border-radius: 6px;
font-size: 0.73rem;
font-family: 'Menlo', 'Consolas', monospace;
line-height: 1.5;
overflow-x: auto;
margin: 0;
}
.fade-enter-active, .fade-leave-active { transition: opacity 0.25s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>