Files

302 lines
9.2 KiB
Vue
Raw Permalink Normal View History

<template>
<div class="iac-concept-demo">
<div class="demo-label">交互演示 手动运维 vs 基础设施即代码</div>
<div class="toggle-bar">
<button
v-for="mode in modes"
:key="mode.key"
:class="['toggle-btn', { active: current === mode.key }]"
@click="current = mode.key"
>
{{ mode.icon }} {{ mode.label }}
</button>
</div>
<div class="scene-container">
<Transition name="fade" mode="out-in">
<div v-if="current === 'manual'" key="manual" class="scene manual-scene">
<div class="scene-title">手动运维流程</div>
<div class="steps">
<div
v-for="(step, i) in manualSteps"
:key="i"
:class="['step-card', { done: manualProgress > i, current: manualProgress === i }]"
>
<div class="step-num">{{ i + 1 }}</div>
<div class="step-icon">{{ step.icon }}</div>
<div class="step-text">{{ step.text }}</div>
<div class="step-risk">{{ step.risk }}</div>
</div>
</div>
<button class="action-btn manual-btn" @click="advanceManual" :disabled="manualProgress >= manualSteps.length">
{{ manualProgress >= manualSteps.length ? '全部完成(耗时约 2 小时)' : '点击控制台按钮...' }}
</button>
<div v-if="manualProgress >= manualSteps.length" class="result-box warning">
手动操作完成但存在风险步骤不可重复无法审计容易遗漏配置
</div>
</div>
<div v-else key="iac" class="scene iac-scene">
<div class="scene-title">IaC 代码驱动流程</div>
<div class="code-block">
<div class="code-header">main.tf</div>
<pre class="code-content"><code>{{ iacCode }}</code></pre>
</div>
<div class="iac-steps">
<div
v-for="(step, i) in iacSteps"
:key="i"
:class="['iac-step', { done: iacProgress > i, current: iacProgress === i }]"
>
<span class="iac-arrow" v-if="i > 0"></span>
<span class="iac-badge">{{ step.icon }}</span>
<span class="iac-label">{{ step.text }}</span>
</div>
</div>
<button class="action-btn iac-btn" @click="advanceIac" :disabled="iacProgress >= iacSteps.length">
{{ iacProgress >= iacSteps.length ? '全部完成(耗时约 30 秒)' : '执行下一步' }}
</button>
<div v-if="iacProgress >= iacSteps.length" class="result-box success">
代码即文档可重复可审计可版本控制团队协作无忧
</div>
</div>
</Transition>
</div>
<div class="comparison-table">
<table>
<thead>
<tr>
<th>对比维度</th>
<th>手动运维</th>
<th>基础设施即代码</th>
</tr>
</thead>
<tbody>
<tr v-for="row in comparisonRows" :key="row.dim">
<td class="dim-cell">{{ row.dim }}</td>
<td class="bad-cell">{{ row.manual }}</td>
<td class="good-cell">{{ row.iac }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const current = ref('manual')
const manualProgress = ref(0)
const iacProgress = ref(0)
const modes = [
{ key: 'manual', icon: '🖱️', label: '手动运维' },
{ key: 'iac', icon: '📝', label: '基础设施即代码' }
]
const manualSteps = [
{ icon: '🌐', text: '登录云控制台', risk: '需要记住密码' },
{ icon: '🖥️', text: '手动创建服务器', risk: '配置可能遗漏' },
{ icon: '🔧', text: '配置安全组规则', risk: '容易开放过多端口' },
{ icon: '💾', text: '挂载存储卷', risk: '大小可能选错' },
{ icon: '🔗', text: '配置负载均衡', risk: '路由规则易出错' },
{ icon: '📋', text: '手动记录到文档', risk: '文档很快过时' }
]
const iacSteps = [
{ icon: '📝', text: 'Write(编写代码)' },
{ icon: '🔍', text: 'Plan(预览变更)' },
{ icon: '🚀', text: 'Apply(自动执行)' },
{ icon: '✅', text: 'Done(状态记录)' }
]
const iacCode = `resource "aws_instance" "web" {
ami = "ami-0c55b159"
instance_type = "t3.micro"
tags = {
Name = "web-server"
Env = "production"
}
}
resource "aws_security_group" "web_sg" {
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}`
const comparisonRows = [
{ dim: '可重复性', manual: '每次操作可能不同', iac: '代码保证完全一致' },
{ dim: '速度', manual: '分钟到小时级', iac: '秒到分钟级' },
{ dim: '审计追踪', manual: '依赖人工记录', iac: 'Git 历史自动记录' },
{ dim: '协作', manual: '口头传达、截图', iac: 'Code Review、PR 流程' },
{ dim: '回滚', manual: '几乎不可能', iac: 'git revert 一键回滚' }
]
function advanceManual() {
if (manualProgress.value < manualSteps.length) {
manualProgress.value++
}
}
function advanceIac() {
if (iacProgress.value < iacSteps.length) {
iacProgress.value++
}
}
</script>
<style scoped>
.iac-concept-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;
}
.toggle-bar {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.toggle-btn {
padding: 6px 16px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.toggle-btn.active {
background: var(--vp-c-brand);
color: #fff;
border-color: var(--vp-c-brand);
}
.scene-container { min-height: 200px; }
.scene-title {
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 0.8rem;
text-align: center;
}
.steps {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 0.5rem;
margin-bottom: 1rem;
}
.step-card {
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.6rem;
background: var(--vp-c-bg);
text-align: center;
transition: all 0.3s;
opacity: 0.5;
}
.step-card.done { opacity: 1; border-color: #f59e0b; background: #fef3c710; }
.step-card.current { opacity: 1; border-color: var(--vp-c-brand); box-shadow: 0 0 0 2px var(--vp-c-brand-light); }
.step-num { font-size: 0.65rem; color: var(--vp-c-text-3); }
.step-icon { font-size: 1.4rem; margin: 4px 0; }
.step-text { font-size: 0.75rem; font-weight: 600; }
.step-risk { font-size: 0.65rem; color: #ef4444; margin-top: 2px; }
.action-btn {
display: block;
margin: 0 auto;
padding: 8px 20px;
border: none;
border-radius: 6px;
font-size: 0.82rem;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:disabled { opacity: 0.6; cursor: default; }
.manual-btn { background: #fbbf24; color: #78350f; }
.iac-btn { background: var(--vp-c-brand); color: #fff; }
.code-block {
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
overflow: hidden;
margin-bottom: 1rem;
}
.code-header {
background: var(--vp-c-bg-alt);
padding: 4px 10px;
font-size: 0.72rem;
font-weight: 600;
color: var(--vp-c-text-2);
border-bottom: 1px solid var(--vp-c-divider);
}
.code-content {
padding: 0.8rem;
font-size: 0.75rem;
line-height: 1.5;
margin: 0;
overflow-x: auto;
background: var(--vp-c-bg);
}
.iac-steps {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.3rem;
margin-bottom: 1rem;
}
.iac-step {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 16px;
font-size: 0.78rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
opacity: 0.4;
transition: all 0.3s;
}
.iac-step.done { opacity: 1; border-color: #10b981; background: #d1fae510; }
.iac-step.current { opacity: 1; border-color: var(--vp-c-brand); box-shadow: 0 0 0 2px var(--vp-c-brand-light); }
.iac-arrow { color: var(--vp-c-text-3); font-size: 0.8rem; }
.result-box {
margin-top: 0.8rem;
padding: 0.6rem 1rem;
border-radius: 6px;
font-size: 0.8rem;
text-align: center;
}
.result-box.warning { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
.result-box.success { background: #d1fae5; color: #065f46; border: 1px solid #6ee7b7; }
:root.dark .result-box.warning { background: #451a0320; color: #fcd34d; }
:root.dark .result-box.success { background: #022c2220; color: #6ee7b7; }
.comparison-table { margin-top: 1rem; overflow-x: auto; }
.comparison-table table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
.comparison-table th, .comparison-table td {
padding: 6px 10px;
border: 1px solid var(--vp-c-divider);
text-align: center;
}
.comparison-table th { background: var(--vp-c-bg-alt); font-weight: 600; }
.dim-cell { font-weight: 600; }
.bad-cell { color: #ef4444; }
.good-cell { color: #10b981; }
.fade-enter-active, .fade-leave-active { transition: opacity 0.25s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>