feat(docs): add interactive cloud services demo components
Add 10 new Vue components for cloud services documentation with interactive demos including: - Cloud services overview and provider comparison - Pricing calculator and region latency visualization - Compute instance configurator and storage type explorer - API call workflow and deployment process steps - IAM structure and policy editor demos
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="iam-structure">
|
||||
<div class="structure-layers">
|
||||
<div
|
||||
v-for="(layer, index) in layers"
|
||||
:key="index"
|
||||
class="layer"
|
||||
:class="{ active: selectedLayer === index }"
|
||||
@click="selectLayer(index)"
|
||||
>
|
||||
<div class="layer-icon">{{ layer.icon }}</div>
|
||||
<div class="layer-content">
|
||||
<div class="layer-name">{{ layer.name }}</div>
|
||||
<div class="layer-desc">{{ layer.shortDesc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedLayerData" class="layer-detail">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ selectedLayerData.icon }}</span>
|
||||
<span class="detail-name">{{ selectedLayerData.name }}</span>
|
||||
</div>
|
||||
<div class="detail-desc">{{ selectedLayerData.description }}</div>
|
||||
<div class="detail-examples">
|
||||
<div class="example-title">示例:</div>
|
||||
<ul>
|
||||
<li v-for="(example, i) in selectedLayerData.examples" :key="i">
|
||||
{{ example }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const selectedLayer = ref(0)
|
||||
|
||||
const layers = [
|
||||
{
|
||||
icon: '👑',
|
||||
name: '根账号',
|
||||
shortDesc: '最高权限',
|
||||
description: '云账号的所有者,拥有全部资源的完全控制权限。建议仅用于初始设置,日常操作使用子账号。',
|
||||
examples: [
|
||||
'创建/删除 IAM 用户',
|
||||
'管理账单和支付方式',
|
||||
'关闭账号',
|
||||
'恢复已删除资源'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '👤',
|
||||
name: 'IAM 用户',
|
||||
shortDesc: '个人身份',
|
||||
description: '为具体人员(如员工)创建的长期凭证,用于日常登录和操作云服务。',
|
||||
examples: [
|
||||
'开发人员账号',
|
||||
'运维人员账号',
|
||||
'只读审计账号',
|
||||
'API 调用账号'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '👥',
|
||||
name: '用户组',
|
||||
shortDesc: '批量管理',
|
||||
description: '将多个用户归为一组,统一分配权限,简化管理。',
|
||||
examples: [
|
||||
'开发组(开发权限)',
|
||||
'运维组(运维权限)',
|
||||
'财务组(账单权限)',
|
||||
'审计组(只读权限)'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '🎭',
|
||||
name: '角色',
|
||||
shortDesc: '临时授权',
|
||||
description: '一种临时身份,可以被切换或赋予其他账号/服务,具有时效性更安全。',
|
||||
examples: [
|
||||
'跨账号访问角色',
|
||||
'服务角色(如 Lambda)',
|
||||
'临时运维角色',
|
||||
'第三方登录角色'
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: '📋',
|
||||
name: '策略',
|
||||
shortDesc: '权限规则',
|
||||
description: '定义"谁可以对什么资源执行什么操作"的规则文档,以 JSON 格式编写。',
|
||||
examples: [
|
||||
'允许访问 S3 存储桶',
|
||||
'禁止删除 EC2 实例',
|
||||
'只允许查看 RDS',
|
||||
'允许特定时间段访问'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const selectedLayerData = computed(() => layers[selectedLayer.value])
|
||||
|
||||
function selectLayer(index) {
|
||||
selectedLayer.value = index
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.iam-structure {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.structure-layers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.layer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.layer:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.layer.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.layer-icon {
|
||||
font-size: 1.25rem;
|
||||
width: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layer-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.layer-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.layer-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.layer-detail {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.detail-examples {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.example-title {
|
||||
font-weight: 500;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-examples ul {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.detail-examples li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.detail-examples li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="policy-editor-demo">
|
||||
<div class="editor-layout">
|
||||
<div class="editor-panel">
|
||||
<div class="panel-title">策略编辑器</div>
|
||||
<div class="action-list">
|
||||
<div
|
||||
v-for="action in actions"
|
||||
:key="action.id"
|
||||
class="action-item"
|
||||
>
|
||||
<label class="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="selectedActions"
|
||||
:value="action.id"
|
||||
>
|
||||
<span>{{ action.name }}</span>
|
||||
</label>
|
||||
<span class="action-desc">{{ action.desc }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-panel">
|
||||
<div class="panel-title">生成的策略</div>
|
||||
<pre><code>{{ generatedPolicy }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="effect-preview">
|
||||
<div class="effect-title">权限效果预览</div>
|
||||
<div class="effect-list">
|
||||
<div
|
||||
v-for="effect in effectList"
|
||||
:key="effect.action"
|
||||
class="effect-item"
|
||||
:class="effect.allowed ? 'allowed' : 'denied'"
|
||||
>
|
||||
<span class="effect-icon">{{ effect.allowed ? '✓' : '✗' }}</span>
|
||||
<span class="effect-text">{{ effect.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const selectedActions = ref(['describe', 'start'])
|
||||
|
||||
const actions = [
|
||||
{ id: 'describe', name: '查看实例', desc: 'DescribeInstances', resource: 'ecs:Describe*' },
|
||||
{ id: 'start', name: '启动实例', desc: 'StartInstance', resource: 'ecs:StartInstance' },
|
||||
{ id: 'stop', name: '停止实例', desc: 'StopInstance', resource: 'ecs:StopInstance' },
|
||||
{ id: 'reboot', name: '重启实例', desc: 'RebootInstance', resource: 'ecs:RebootInstance' },
|
||||
{ id: 'create', name: '创建实例', desc: 'CreateInstance', resource: 'ecs:CreateInstance' },
|
||||
{ id: 'delete', name: '删除实例', desc: 'DeleteInstance', resource: 'ecs:DeleteInstance' }
|
||||
]
|
||||
|
||||
const generatedPolicy = computed(() => {
|
||||
const selected = actions.filter(a => selectedActions.value.includes(a.id))
|
||||
const actionList = selected.map(a => a.resource)
|
||||
|
||||
return JSON.stringify({
|
||||
Version: "1",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Action: actionList,
|
||||
Resource: "*"
|
||||
}
|
||||
]
|
||||
}, null, 2)
|
||||
})
|
||||
|
||||
const effectList = computed(() => {
|
||||
return actions.map(action => ({
|
||||
name: action.name,
|
||||
action: action.id,
|
||||
allowed: selectedActions.value.includes(action.id)
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.policy-editor-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.editor-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor-panel,
|
||||
.preview-panel {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.action-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.4rem 0;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.checkbox input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.preview-panel pre {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.preview-panel code {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.effect-preview {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.effect-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.effect-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.effect-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.effect-item.allowed {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.effect-item.denied {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.effect-icon {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.editor-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.effect-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="api-call-demo">
|
||||
<div class="flow-steps">
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="step"
|
||||
:class="{ active: currentStep >= index, completed: currentStep > index }"
|
||||
>
|
||||
<div class="step-num">{{ index + 1 }}</div>
|
||||
<div class="step-content">
|
||||
<div class="step-title">{{ step.title }}</div>
|
||||
<div class="step-desc">{{ step.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-panel">
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="nextStep"
|
||||
:disabled="currentStep >= steps.length"
|
||||
>
|
||||
{{ currentStep >= steps.length ? '已完成' : '下一步' }}
|
||||
</button>
|
||||
<button class="action-btn outline" @click="reset">
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStep > 0" class="code-preview">
|
||||
<div class="code-title">{{ steps[currentStep - 1].codeTitle }}</div>
|
||||
<pre><code>{{ steps[currentStep - 1].code }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const currentStep = ref(0)
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: '获取 AccessKey',
|
||||
desc: '在控制台创建 AccessKey ID 和 Secret',
|
||||
codeTitle: '配置凭证',
|
||||
code: `// 环境变量设置
|
||||
export ALIYUN_ACCESS_KEY_ID=your_key_id
|
||||
export ALIYUN_ACCESS_KEY_SECRET=your_secret`
|
||||
},
|
||||
{
|
||||
title: '安装 SDK',
|
||||
desc: '安装对应语言的云服务 SDK',
|
||||
codeTitle: '安装依赖',
|
||||
code: `# Python
|
||||
pip install alibabacloud-ecs20140526
|
||||
|
||||
# Node.js
|
||||
npm install @alicloud/ecs20140526`
|
||||
},
|
||||
{
|
||||
title: '编写调用代码',
|
||||
desc: '使用 SDK 调用云服务 API',
|
||||
codeTitle: '调用示例',
|
||||
code: `from alibabacloud_ecs20140526 import models as ecs_models
|
||||
|
||||
# 创建客户端
|
||||
client = create_client()
|
||||
|
||||
# 调用 API
|
||||
response = client.describe_instances(
|
||||
ecs_models.DescribeInstancesRequest()
|
||||
)
|
||||
|
||||
print(response.body)`
|
||||
},
|
||||
{
|
||||
title: '处理响应',
|
||||
desc: '解析 API 返回的数据',
|
||||
codeTitle: '处理结果',
|
||||
code: `// 解析响应
|
||||
instances = response.body.instances.instance
|
||||
|
||||
for inst in instances:
|
||||
print(f"ID: {inst.instance_id}")
|
||||
print(f"状态: {inst.status}")
|
||||
print(f"IP: {inst.public_ip_address}")`
|
||||
}
|
||||
]
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep.value < steps.length) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
currentStep.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.api-call-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.flow-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
opacity: 0.5;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
opacity: 1;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.step.completed {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step.active .step-num {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step.completed .step-num {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.action-panel {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--vp-c-brand);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn.outline {
|
||||
background: transparent;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.code-preview {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.code-title {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="cloud-history-demo">
|
||||
<div class="timeline">
|
||||
<div
|
||||
v-for="(event, index) in events"
|
||||
:key="index"
|
||||
class="timeline-item"
|
||||
:class="{ active: selectedEvent === index }"
|
||||
@click="selectedEvent = index"
|
||||
>
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-year">{{ event.year }}</div>
|
||||
<div class="timeline-title">{{ event.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedEventData" class="event-detail">
|
||||
<div class="detail-year">{{ selectedEventData.year }}</div>
|
||||
<div class="detail-title">{{ selectedEventData.title }}</div>
|
||||
<div class="detail-desc">{{ selectedEventData.description }}</div>
|
||||
<div class="detail-impact">
|
||||
<span class="impact-label">影响:</span>
|
||||
<span class="impact-text">{{ selectedEventData.impact }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const selectedEvent = ref(3)
|
||||
|
||||
const events = [
|
||||
{
|
||||
year: '1960s',
|
||||
title: '概念萌芽',
|
||||
description: 'J.C.R. Licklider 提出"星际计算机网络"设想,是云计算概念的最早雏形。',
|
||||
impact: '奠定了分布式计算的理论基础'
|
||||
},
|
||||
{
|
||||
year: '1990s',
|
||||
title: '虚拟化技术',
|
||||
description: 'VMware 推出 x86 虚拟化技术,允许在一台物理机上运行多个虚拟机。',
|
||||
impact: '为云计算的资源池化提供了技术基础'
|
||||
},
|
||||
{
|
||||
year: '2006',
|
||||
title: 'AWS 诞生',
|
||||
description: 'Amazon 推出 EC2 和 S3,标志着现代云计算服务的正式诞生。',
|
||||
impact: '开创了公有云服务的商业模式'
|
||||
},
|
||||
{
|
||||
year: '2009',
|
||||
title: '阿里云成立',
|
||||
description: '阿里巴巴成立阿里云,成为中国最早的云计算服务商。',
|
||||
impact: '推动了中国云计算市场的发展'
|
||||
},
|
||||
{
|
||||
year: '2010s',
|
||||
title: '云原生时代',
|
||||
description: 'Docker、Kubernetes 等技术兴起,微服务架构成为主流。',
|
||||
impact: '改变了应用开发和部署的方式'
|
||||
},
|
||||
{
|
||||
year: '2020s',
|
||||
title: 'AI 云时代',
|
||||
description: '大模型和 AI 服务成为云厂商的核心竞争力,Serverless 普及。',
|
||||
impact: '云计算进入智能化新阶段'
|
||||
}
|
||||
]
|
||||
|
||||
const selectedEventData = computed(() => events[selectedEvent.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cloud-history-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.timeline-item:hover {
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.timeline-item.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-divider);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.timeline-item.active .timeline-dot {
|
||||
background: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timeline-year {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.event-detail {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.detail-year {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-impact {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.impact-label {
|
||||
color: var(--vp-c-text-2);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.impact-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="cloud-services-overview">
|
||||
<div class="services-grid">
|
||||
<div
|
||||
v-for="service in services"
|
||||
:key="service.id"
|
||||
class="service-card"
|
||||
:class="{ active: selectedService === service.id }"
|
||||
@click="selectService(service.id)"
|
||||
>
|
||||
<div class="service-icon">{{ service.icon }}</div>
|
||||
<div class="service-name">{{ service.name }}</div>
|
||||
<div class="service-examples">{{ service.examples }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedServiceData" class="service-detail">
|
||||
<div class="detail-title">{{ selectedServiceData.name }}</div>
|
||||
<div class="detail-desc">{{ selectedServiceData.description }}</div>
|
||||
<div class="detail-compare">
|
||||
<div class="compare-item">
|
||||
<span class="label">AWS:</span>
|
||||
<span class="value">{{ selectedServiceData.aws }}</span>
|
||||
</div>
|
||||
<div class="compare-item">
|
||||
<span class="label">阿里云:</span>
|
||||
<span class="value">{{ selectedServiceData.aliyun }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const selectedService = ref(null)
|
||||
|
||||
const services = [
|
||||
{
|
||||
id: 'compute',
|
||||
icon: '⚙️',
|
||||
name: '计算',
|
||||
examples: 'EC2 / ECS',
|
||||
description: '提供虚拟服务器和计算能力,是云服务的基础',
|
||||
aws: 'Amazon EC2',
|
||||
aliyun: 'ECS 云服务器'
|
||||
},
|
||||
{
|
||||
id: 'storage',
|
||||
icon: '💾',
|
||||
name: '存储',
|
||||
examples: 'S3 / OSS',
|
||||
description: '对象存储服务,用于存放图片、文档等文件',
|
||||
aws: 'Amazon S3',
|
||||
aliyun: 'OSS 对象存储'
|
||||
},
|
||||
{
|
||||
id: 'network',
|
||||
icon: '🌐',
|
||||
name: '网络',
|
||||
examples: 'VPC / 专有网络',
|
||||
description: '构建隔离的虚拟网络环境',
|
||||
aws: 'Amazon VPC',
|
||||
aliyun: '专有网络 VPC'
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
icon: '🗄️',
|
||||
name: '数据库',
|
||||
examples: 'RDS / PolarDB',
|
||||
description: '托管的关系型数据库服务',
|
||||
aws: 'Amazon RDS',
|
||||
aliyun: 'RDS 关系型数据库'
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
icon: '🔒',
|
||||
name: '安全',
|
||||
examples: 'IAM / RAM',
|
||||
description: '身份认证和访问控制服务',
|
||||
aws: 'AWS IAM',
|
||||
aliyun: 'RAM 访问控制'
|
||||
},
|
||||
{
|
||||
id: 'middleware',
|
||||
icon: '🔧',
|
||||
name: '中间件',
|
||||
examples: 'MQ / RocketMQ',
|
||||
description: '消息队列和缓存服务',
|
||||
aws: 'Amazon MQ',
|
||||
aliyun: 'RocketMQ'
|
||||
}
|
||||
]
|
||||
|
||||
const selectedServiceData = computed(() =>
|
||||
services.find(s => s.id === selectedService.value)
|
||||
)
|
||||
|
||||
function selectService(id) {
|
||||
selectedService.value = selectedService.value === id ? null : id
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cloud-services-overview {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.service-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.service-examples {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.service-detail {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-compare {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.compare-item {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.compare-item .label {
|
||||
color: var(--vp-c-text-2);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.compare-item .value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.detail-compare {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="compute-instance-demo">
|
||||
<div class="config-panel">
|
||||
<div class="config-row">
|
||||
<label>地域</label>
|
||||
<div class="options">
|
||||
<button
|
||||
v-for="region in regions"
|
||||
:key="region.id"
|
||||
:class="{ active: config.region === region.id }"
|
||||
@click="config.region = region.id"
|
||||
>
|
||||
{{ region.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label>规格</label>
|
||||
<div class="options">
|
||||
<button
|
||||
v-for="spec in specs"
|
||||
:key="spec.id"
|
||||
:class="{ active: config.spec === spec.id }"
|
||||
@click="config.spec = spec.id"
|
||||
>
|
||||
{{ spec.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label>镜像</label>
|
||||
<div class="options">
|
||||
<button
|
||||
v-for="image in images"
|
||||
:key="image.id"
|
||||
:class="{ active: config.image === image.id }"
|
||||
@click="config.image = image.id"
|
||||
>
|
||||
{{ image.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-panel">
|
||||
<div class="result-title">配置结果</div>
|
||||
<div class="result-grid">
|
||||
<div class="result-item">
|
||||
<span class="label">配置</span>
|
||||
<span class="value">{{ selectedSpec?.name }} / {{ selectedImage?.name }}</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span class="label">预估价格</span>
|
||||
<span class="value price">¥{{ price }}/月</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span class="label">适用场景</span>
|
||||
<span class="value">{{ selectedSpec?.scene }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const config = ref({
|
||||
region: 'hangzhou',
|
||||
spec: 'medium',
|
||||
image: 'ubuntu'
|
||||
})
|
||||
|
||||
const regions = [
|
||||
{ id: 'hangzhou', name: '华东-杭州' },
|
||||
{ id: 'beijing', name: '华北-北京' },
|
||||
{ id: 'shenzhen', name: '华南-深圳' },
|
||||
{ id: 'singapore', name: '亚太-新加坡' }
|
||||
]
|
||||
|
||||
const specs = [
|
||||
{ id: 'small', name: '1核2G', scene: '测试环境、个人博客', price: 89 },
|
||||
{ id: 'medium', name: '2核4G', scene: '中小型应用、开发环境', price: 199 },
|
||||
{ id: 'large', name: '4核8G', scene: '生产环境、中型网站', price: 399 },
|
||||
{ id: 'xlarge', name: '8核16G', scene: '大型应用、数据库', price: 799 }
|
||||
]
|
||||
|
||||
const images = [
|
||||
{ id: 'ubuntu', name: 'Ubuntu 22.04' },
|
||||
{ id: 'centos', name: 'CentOS 7.9' },
|
||||
{ id: 'windows', name: 'Windows Server' },
|
||||
{ id: 'alpine', name: 'Alpine Linux' }
|
||||
]
|
||||
|
||||
const selectedSpec = computed(() => specs.find(s => s.id === config.value.spec))
|
||||
const selectedImage = computed(() => images.find(i => i.id === config.value.image))
|
||||
const price = computed(() => selectedSpec.value?.price || 0)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.compute-instance-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.config-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.config-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.config-row label {
|
||||
width: 50px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.options button {
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.options button:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.options button.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.result-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.result-item .label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.result-item .value {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.result-item .price {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.result-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.config-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.config-row label {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="deploy-workflow-demo">
|
||||
<div class="workflow-steps">
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="step-card"
|
||||
:class="{ active: currentStep === index, completed: currentStep > index }"
|
||||
@click="currentStep = index"
|
||||
>
|
||||
<div class="step-number">{{ index + 1 }}</div>
|
||||
<div class="step-info">
|
||||
<div class="step-name">{{ step.name }}</div>
|
||||
<div class="step-time">{{ step.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStepData" class="step-detail">
|
||||
<div class="detail-header">
|
||||
<span class="detail-step">步骤 {{ currentStep + 1 }}</span>
|
||||
<span class="detail-name">{{ currentStepData.name }}</span>
|
||||
</div>
|
||||
<div class="detail-content">
|
||||
<div class="detail-desc">{{ currentStepData.description }}</div>
|
||||
<div class="detail-tasks">
|
||||
<div class="tasks-title">具体操作:</div>
|
||||
<ul>
|
||||
<li v-for="(task, i) in currentStepData.tasks" :key="i">{{ task }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workflow-actions">
|
||||
<button class="action-btn" @click="prevStep" :disabled="currentStep === 0">
|
||||
上一步
|
||||
</button>
|
||||
<button class="action-btn primary" @click="nextStep" :disabled="currentStep >= steps.length - 1">
|
||||
{{ currentStep >= steps.length - 1 ? '完成' : '下一步' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const currentStep = ref(0)
|
||||
|
||||
const steps = [
|
||||
{
|
||||
name: '准备代码',
|
||||
time: '5分钟',
|
||||
description: '将网站代码打包成可部署的格式',
|
||||
tasks: [
|
||||
'整理 HTML/CSS/JS 文件',
|
||||
'压缩图片和静态资源',
|
||||
'检查文件路径是否正确'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '创建存储桶',
|
||||
time: '2分钟',
|
||||
description: '在对象存储服务中创建存储空间',
|
||||
tasks: [
|
||||
'登录云控制台',
|
||||
'进入对象存储 OSS/S3',
|
||||
'点击"创建 Bucket"',
|
||||
'设置 Bucket 名称和地域'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '上传文件',
|
||||
time: '3分钟',
|
||||
description: '将网站文件上传到存储桶',
|
||||
tasks: [
|
||||
'进入 Bucket 管理页面',
|
||||
'点击"上传文件"',
|
||||
'选择本地网站文件',
|
||||
'等待上传完成'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '配置 CDN',
|
||||
time: '5分钟',
|
||||
description: '配置内容分发网络加速访问',
|
||||
tasks: [
|
||||
'进入 CDN 控制台',
|
||||
'添加加速域名',
|
||||
'配置源站为存储桶',
|
||||
'等待 CDN 部署完成'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '域名绑定',
|
||||
time: '10分钟',
|
||||
description: '将自定义域名绑定到 CDN',
|
||||
tasks: [
|
||||
'添加域名解析记录',
|
||||
'配置 CNAME 到 CDN',
|
||||
'申请 SSL 证书',
|
||||
'测试 HTTPS 访问'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const currentStepData = computed(() => steps[currentStep.value])
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep.value < steps.length - 1) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (currentStep.value > 0) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.deploy-workflow-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.workflow-steps {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.step-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.step-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.step-card.completed {
|
||||
border-color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-divider);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-card.active .step-number {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-card.completed .step-number {
|
||||
background: #22c55e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-weight: 500;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.step-time {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.step-detail {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-step {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.tasks-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.detail-tasks ul {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.detail-tasks li {
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.workflow-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:hover:not(:disabled) {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.primary:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div class="pricing-calculator">
|
||||
<div class="config-section">
|
||||
<div class="config-row">
|
||||
<span class="label">实例规格</span>
|
||||
<select v-model="config.spec">
|
||||
<option value="small">1核2G (入门)</option>
|
||||
<option value="medium">2核4G (标准)</option>
|
||||
<option value="large">4核8G (高性能)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<span class="label">运行时长</span>
|
||||
<input type="range" v-model.number="config.hours" min="1" max="24" />
|
||||
<span class="value">{{ config.hours }} 小时/天</span>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<span class="label">运行天数</span>
|
||||
<input type="range" v-model.number="config.days" min="1" max="31" />
|
||||
<span class="value">{{ config.days }} 天/月</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-section">
|
||||
<div class="result-header">月度成本对比</div>
|
||||
<div class="result-cards">
|
||||
<div class="result-card">
|
||||
<div class="model">按需付费</div>
|
||||
<div class="price">${{ costs.ondemand }}/月</div>
|
||||
</div>
|
||||
<div class="result-card recommended">
|
||||
<div class="model">预留实例</div>
|
||||
<div class="price">${{ costs.reserved }}/月</div>
|
||||
<div class="saving">省 {{ savings }}%</div>
|
||||
</div>
|
||||
<div class="result-card">
|
||||
<div class="model">抢占式</div>
|
||||
<div class="price">${{ costs.spot }}/月</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tip-box">
|
||||
<span class="tip-icon">💡</span>
|
||||
<span class="tip-text">{{ recommendation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const config = ref({
|
||||
spec: 'medium',
|
||||
hours: 12,
|
||||
days: 22
|
||||
})
|
||||
|
||||
const specPrices = {
|
||||
small: { ondemand: 0.08, reserved: 45, spot: 0.024 },
|
||||
medium: { ondemand: 0.16, reserved: 89, spot: 0.048 },
|
||||
large: { ondemand: 0.32, reserved: 179, spot: 0.096 }
|
||||
}
|
||||
|
||||
const costs = computed(() => {
|
||||
const price = specPrices[config.value.spec]
|
||||
const monthlyHours = config.value.hours * config.value.days
|
||||
|
||||
return {
|
||||
ondemand: Math.round(price.ondemand * monthlyHours),
|
||||
reserved: price.reserved,
|
||||
spot: Math.round(price.spot * monthlyHours)
|
||||
}
|
||||
})
|
||||
|
||||
const savings = computed(() => {
|
||||
const save = costs.value.ondemand - costs.value.reserved
|
||||
return Math.round((save / costs.value.ondemand) * 100)
|
||||
})
|
||||
|
||||
const recommendation = computed(() => {
|
||||
if (config.value.days < 15) {
|
||||
return '当前使用频率较低,建议选择按需付费'
|
||||
} else if (savings.value > 30) {
|
||||
return `当前使用负载稳定,切换预留实例可省 ${savings.value}%`
|
||||
} else {
|
||||
return '根据当前配置,预留实例更具成本优势'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pricing-calculator {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.config-section {
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.config-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.config-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.config-row .label {
|
||||
width: 70px;
|
||||
color: var(--vp-c-text-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.config-row select {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.config-row input[type="range"] {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.config-row .value {
|
||||
width: 85px;
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.result-header {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.result-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-card.recommended {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.result-card .model {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.result-card .price {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-card .saving {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-brand);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.tip-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.result-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.config-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.config-row .label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.config-row .value {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="provider-comparison">
|
||||
<div class="compare-table">
|
||||
<div class="table-header">
|
||||
<div class="col feature">对比项</div>
|
||||
<div class="col provider">AWS</div>
|
||||
<div class="col provider">阿里云</div>
|
||||
<div class="col provider">腾讯云</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="row in compareData"
|
||||
:key="row.feature"
|
||||
class="table-row"
|
||||
>
|
||||
<div class="col feature">{{ row.feature }}</div>
|
||||
<div class="col provider" :class="{ highlight: row.awsHighlight }">
|
||||
{{ row.aws }}
|
||||
</div>
|
||||
<div class="col provider" :class="{ highlight: row.aliyunHighlight }">
|
||||
{{ row.aliyun }}
|
||||
</div>
|
||||
<div class="col provider" :class="{ highlight: row.tencentHighlight }">
|
||||
{{ row.tencent }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="selection-guide">
|
||||
<div class="guide-title">💡 选择建议</div>
|
||||
<div class="guide-items">
|
||||
<div class="guide-item">
|
||||
<span class="scenario">出海业务</span>
|
||||
<span class="recommend">→ AWS</span>
|
||||
</div>
|
||||
<div class="guide-item">
|
||||
<span class="scenario">国内电商</span>
|
||||
<span class="recommend">→ 阿里云</span>
|
||||
</div>
|
||||
<div class="guide-item">
|
||||
<span class="scenario">游戏/社交</span>
|
||||
<span class="recommend">→ 腾讯云</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const compareData = [
|
||||
{
|
||||
feature: '全球覆盖',
|
||||
aws: '⭐⭐⭐⭐⭐',
|
||||
aliyun: '⭐⭐⭐',
|
||||
tencent: '⭐⭐⭐',
|
||||
awsHighlight: true
|
||||
},
|
||||
{
|
||||
feature: '国内速度',
|
||||
aws: '⭐⭐⭐',
|
||||
aliyun: '⭐⭐⭐⭐⭐',
|
||||
tencent: '⭐⭐⭐⭐⭐',
|
||||
aliyunHighlight: true,
|
||||
tencentHighlight: true
|
||||
},
|
||||
{
|
||||
feature: '文档中文',
|
||||
aws: '⭐⭐⭐',
|
||||
aliyun: '⭐⭐⭐⭐⭐',
|
||||
tencent: '⭐⭐⭐⭐⭐',
|
||||
aliyunHighlight: true,
|
||||
tencentHighlight: true
|
||||
},
|
||||
{
|
||||
feature: '价格优势',
|
||||
aws: '⭐⭐⭐',
|
||||
aliyun: '⭐⭐⭐⭐',
|
||||
tencent: '⭐⭐⭐⭐⭐',
|
||||
tencentHighlight: true
|
||||
},
|
||||
{
|
||||
feature: '生态丰富',
|
||||
aws: '⭐⭐⭐⭐⭐',
|
||||
aliyun: '⭐⭐⭐⭐',
|
||||
tencent: '⭐⭐⭐⭐',
|
||||
awsHighlight: true
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.provider-comparison {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.compare-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.table-header,
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.4rem 0;
|
||||
}
|
||||
|
||||
.col {
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.col.feature {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.col.provider {
|
||||
text-align: center;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.col.provider.highlight {
|
||||
background: var(--vp-c-brand-soft);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selection-guide {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.guide-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.guide-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.guide-item .scenario {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.guide-item .recommend {
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.table-header,
|
||||
.table-row {
|
||||
grid-template-columns: 80px 1fr 1fr 1fr;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.col {
|
||||
padding: 0.3rem 0.4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="region-latency-demo">
|
||||
<div class="user-location">
|
||||
<label>你的位置:</label>
|
||||
<div class="location-options">
|
||||
<button
|
||||
v-for="loc in locations"
|
||||
:key="loc.id"
|
||||
:class="{ active: userLocation === loc.id }"
|
||||
@click="userLocation = loc.id"
|
||||
>
|
||||
{{ loc.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="latency-table">
|
||||
<div class="table-header">
|
||||
<div class="col region">云厂商地域</div>
|
||||
<div class="col latency">延迟</div>
|
||||
<div class="col rating">推荐度</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in latencyData"
|
||||
:key="item.region"
|
||||
class="table-row"
|
||||
:class="{ best: item.rating === '⭐⭐⭐' }"
|
||||
>
|
||||
<div class="col region">{{ item.region }}</div>
|
||||
<div class="col latency">
|
||||
<div class="latency-bar">
|
||||
<div class="bar-fill" :style="{ width: item.percent + '%' }"></div>
|
||||
<span class="latency-value">{{ item.latency }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col rating">{{ item.rating }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recommendation">
|
||||
<span class="rec-icon">💡</span>
|
||||
<span class="rec-text">{{ recommendation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const userLocation = ref('beijing')
|
||||
|
||||
const locations = [
|
||||
{ id: 'beijing', name: '北京' },
|
||||
{ id: 'shanghai', name: '上海' },
|
||||
{ id: 'guangzhou', name: '广州' },
|
||||
{ id: 'chengdu', name: '成都' }
|
||||
]
|
||||
|
||||
const latencyMap = {
|
||||
beijing: [
|
||||
{ region: '华北-北京', latency: 15, rating: '⭐⭐⭐' },
|
||||
{ region: '华东-上海', latency: 35, rating: '⭐⭐' },
|
||||
{ region: '华南-广州', latency: 55, rating: '⭐' },
|
||||
{ region: '亚太-新加坡', latency: 85, rating: '⭐' }
|
||||
],
|
||||
shanghai: [
|
||||
{ region: '华东-上海', latency: 12, rating: '⭐⭐⭐' },
|
||||
{ region: '华北-北京', latency: 38, rating: '⭐⭐' },
|
||||
{ region: '华南-广州', latency: 45, rating: '⭐⭐' },
|
||||
{ region: '亚太-新加坡', latency: 75, rating: '⭐' }
|
||||
],
|
||||
guangzhou: [
|
||||
{ region: '华南-广州', latency: 10, rating: '⭐⭐⭐' },
|
||||
{ region: '华东-上海', latency: 42, rating: '⭐⭐' },
|
||||
{ region: '华北-北京', latency: 58, rating: '⭐' },
|
||||
{ region: '亚太-新加坡', latency: 45, rating: '⭐⭐' }
|
||||
],
|
||||
chengdu: [
|
||||
{ region: '华东-上海', latency: 40, rating: '⭐⭐' },
|
||||
{ region: '华北-北京', latency: 48, rating: '⭐⭐' },
|
||||
{ region: '华南-广州', latency: 52, rating: '⭐' },
|
||||
{ region: '西南-成都', latency: 8, rating: '⭐⭐⭐' }
|
||||
]
|
||||
}
|
||||
|
||||
const latencyData = computed(() => {
|
||||
const data = latencyMap[userLocation.value] || latencyMap.beijing
|
||||
const maxLatency = Math.max(...data.map(d => d.latency))
|
||||
return data.map(d => ({
|
||||
...d,
|
||||
percent: (d.latency / maxLatency) * 100
|
||||
}))
|
||||
})
|
||||
|
||||
const recommendation = computed(() => {
|
||||
const best = latencyData.value.find(d => d.rating === '⭐⭐⭐')
|
||||
return `建议选择 ${best?.region},延迟最低 (${best?.latency}ms)`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.region-latency-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.user-location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.user-location label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.location-options {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.location-options button {
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.location-options button:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.location-options button.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.latency-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.table-header,
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr 60px;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
font-size: 0.85rem;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.table-row.best {
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.col.region {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.latency-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
height: 20px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
background: var(--vp-c-brand);
|
||||
border-radius: 8px;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.latency-value {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
min-width: 45px;
|
||||
}
|
||||
|
||||
.recommendation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.rec-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.rec-text {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.user-location {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.table-header,
|
||||
.table-row {
|
||||
grid-template-columns: 80px 1fr 50px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="storage-type-demo">
|
||||
<div class="type-cards">
|
||||
<div
|
||||
v-for="type in storageTypes"
|
||||
:key="type.id"
|
||||
class="type-card"
|
||||
:class="{ active: selectedType === type.id }"
|
||||
@click="selectedType = type.id"
|
||||
>
|
||||
<div class="type-icon">{{ type.icon }}</div>
|
||||
<div class="type-name">{{ type.name }}</div>
|
||||
<div class="type-example">{{ type.example }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTypeData" class="type-detail">
|
||||
<div class="detail-row">
|
||||
<span class="label">特点</span>
|
||||
<span class="value">{{ selectedTypeData.features }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">适用场景</span>
|
||||
<span class="value">{{ selectedTypeData.scenarios }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">计费方式</span>
|
||||
<span class="value">{{ selectedTypeData.pricing }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const selectedType = ref('object')
|
||||
|
||||
const storageTypes = [
|
||||
{
|
||||
id: 'object',
|
||||
icon: '📦',
|
||||
name: '对象存储',
|
||||
example: 'S3 / OSS',
|
||||
features: '海量存储、高可靠、低成本',
|
||||
scenarios: '图片、视频、备份、静态网站',
|
||||
pricing: '按存储容量 + 请求次数'
|
||||
},
|
||||
{
|
||||
id: 'block',
|
||||
icon: '💽',
|
||||
name: '块存储',
|
||||
example: 'EBS / 云盘',
|
||||
features: '低延迟、高性能、可挂载',
|
||||
scenarios: '数据库、文件系统、操作系统',
|
||||
pricing: '按容量 + IOPS'
|
||||
},
|
||||
{
|
||||
id: 'file',
|
||||
icon: '📁',
|
||||
name: '文件存储',
|
||||
example: 'EFS / NAS',
|
||||
features: '共享访问、POSIX 兼容',
|
||||
scenarios: '共享文件、内容管理、HPC',
|
||||
pricing: '按容量 + 吞吐'
|
||||
},
|
||||
{
|
||||
id: 'archive',
|
||||
icon: '🗃️',
|
||||
name: '归档存储',
|
||||
example: 'Glacier / 归档',
|
||||
features: '极低成本、取回慢',
|
||||
scenarios: '冷数据、合规备份、长期归档',
|
||||
pricing: '按容量,取回额外收费'
|
||||
}
|
||||
]
|
||||
|
||||
const selectedTypeData = computed(() =>
|
||||
storageTypes.find(t => t.id === selectedType.value)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.storage-type-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.type-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.type-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.type-card:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.type-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.type-example {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.type-detail {
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.detail-row .label {
|
||||
color: var(--vp-c-text-2);
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-row .value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.type-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -350,6 +350,21 @@ import ServiceSelectionDemo from './components/appendix/cloud-services/ServiceSe
|
||||
import DatabaseServicesDemo from './components/appendix/cloud-services/DatabaseServicesDemo.vue'
|
||||
import K8sServicesDemo from './components/appendix/cloud-services/K8sServicesDemo.vue'
|
||||
|
||||
// Cloud Services Simple Components (new)
|
||||
import CloudServicesOverview from './components/appendix/cloud-services/CloudServicesOverview.vue'
|
||||
import ProviderComparison from './components/appendix/cloud-services/ProviderComparison.vue'
|
||||
import PricingCalculator from './components/appendix/cloud-services/PricingCalculator.vue'
|
||||
import ComputeInstanceDemo from './components/appendix/cloud-services/ComputeInstanceDemo.vue'
|
||||
import StorageTypeDemo from './components/appendix/cloud-services/StorageTypeDemo.vue'
|
||||
import ApiCallDemo from './components/appendix/cloud-services/ApiCallDemo.vue'
|
||||
import CloudHistoryDemo from './components/appendix/cloud-services/CloudHistoryDemo.vue'
|
||||
import DeployWorkflowDemo from './components/appendix/cloud-services/DeployWorkflowDemo.vue'
|
||||
import RegionLatencyDemo from './components/appendix/cloud-services/RegionLatencyDemo.vue'
|
||||
|
||||
// Cloud IAM Simple Components (new)
|
||||
import IAMStructure from './components/appendix/cloud-iam/IAMStructure.vue'
|
||||
import PolicyEditorDemo from './components/appendix/cloud-iam/PolicyEditorDemo.vue'
|
||||
|
||||
// Gateway Proxy Components
|
||||
import ReverseProxyDemo from './components/appendix/gateway-proxy/ReverseProxyDemo.vue'
|
||||
import ApiGatewayDemo from './components/appendix/gateway-proxy/ApiGatewayDemo.vue'
|
||||
@@ -784,6 +799,21 @@ export default {
|
||||
app.component('DatabaseServicesDemo', DatabaseServicesDemo)
|
||||
app.component('K8sServicesDemo', K8sServicesDemo)
|
||||
|
||||
// Cloud Services Simple Components Registration (new)
|
||||
app.component('CloudServicesOverview', CloudServicesOverview)
|
||||
app.component('ProviderComparison', ProviderComparison)
|
||||
app.component('PricingCalculator', PricingCalculator)
|
||||
app.component('ComputeInstanceDemo', ComputeInstanceDemo)
|
||||
app.component('StorageTypeDemo', StorageTypeDemo)
|
||||
app.component('ApiCallDemo', ApiCallDemo)
|
||||
app.component('CloudHistoryDemo', CloudHistoryDemo)
|
||||
app.component('DeployWorkflowDemo', DeployWorkflowDemo)
|
||||
app.component('RegionLatencyDemo', RegionLatencyDemo)
|
||||
|
||||
// Cloud IAM Simple Components Registration (new)
|
||||
app.component('IAMStructure', IAMStructure)
|
||||
app.component('PolicyEditorDemo', PolicyEditorDemo)
|
||||
|
||||
// Cloud IAM Components Registration
|
||||
app.component('IamRamComparisonDemo', IamRamComparisonDemo)
|
||||
app.component('IdentityProviderDemo', IdentityProviderDemo)
|
||||
|
||||
Reference in New Issue
Block a user