Files
test-repo/docs/.vitepress/theme/components/appendix/cloud-topology/SubnetDesignDemo.vue
T
sanbuphy 0eba9e87e9 fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier)
- Relaxed strict Vue/JS rules for demo code compatibility
- Fix syntax errors in ApiPlayground and VoiceCloningDemo
- Fix duplicate else-if condition in ApiPlayground
- Fix Promise executor async pattern in AutoregressiveAudioDemo
- Add TypeScript file support to ESLint config

Warnings reduced from 295 to 251 problems.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 17:38:10 +08:00

543 lines
12 KiB
Vue
Raw 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="subnet-design-demo">
<!-- 控制面板 -->
<div class="control-panel">
<div class="panel-section">
<span class="panel-label">VPC 网段</span>
<el-radio-group
v-model="vpcCidr"
size="small"
>
<el-radio-button label="172.16.0.0/12">
172.16.0.0/12
</el-radio-button>
<el-radio-button label="10.0.0.0/8">
10.0.0.0/8
</el-radio-button>
<el-radio-button label="192.168.0.0/16">
192.168.0.0/16
</el-radio-button>
</el-radio-group>
</div>
<div class="panel-section">
<span class="panel-label">子网划分</span>
<el-slider
v-model="subnetBits"
:min="2"
:max="4"
show-stops
:marks="{2: '/24', 3: '/25', 4: '/26'}"
style="width: 200px;"
/>
</div>
<el-switch
v-model="showCalculation"
active-text="显示计算过程"
style="margin-left: 20px;"
/>
</div>
<!-- 网段可视化 -->
<div class="network-visualization">
<div class="vpc-block">
<div class="vpc-header">
<span class="vpc-name">VPC 网段</span>
<span class="vpc-cidr">{{ vpcCidr }}</span>
<span class="vpc-stats">可用 IP: {{ totalIps.toLocaleString() }} </span>
</div>
<div class="subnet-grid">
<div
v-for="(subnet, index) in subnets"
:key="index"
class="subnet-cell"
:class="[subnet.type, { active: selectedSubnet === index }]"
@click="selectSubnet(index)"
@mouseenter="hoverSubnet = index"
@mouseleave="hoverSubnet = null"
>
<div class="cell-header">
<span class="cell-type">{{ subnet.type === 'public' ? '🌐' : '🔒' }}</span>
<span class="cell-name">{{ subnet.name }}</span>
</div>
<div class="cell-cidr">
{{ subnet.cidr }}
</div>
<div class="cell-stats">
<span class="ip-count">{{ subnet.ipCount }} IP</span>
<span class="az-badge">{{ subnet.az }}</span>
</div>
<!-- 悬停提示 -->
<div
v-if="hoverSubnet === index && showCalculation"
class="cell-tooltip"
>
<div class="tooltip-row">
<span>网段范围</span>
<code>{{ subnet.range }}</code>
</div>
<div class="tooltip-row">
<span>可用 IP</span>
<span>{{ subnet.usableIps }} </span>
</div>
<div class="tooltip-row">
<span>预留 IP</span>
<span>网络地址 + 广播地址 + 网关</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 网段计算说明 -->
<div
v-if="showCalculation"
class="calculation-panel"
>
<h4>📐 子网划分计算说明</h4>
<div class="calc-section">
<h5>1. 基础概念</h5>
<div class="concept-grid">
<div class="concept-item">
<span class="concept-label">CIDR 表示法</span>
<code>/24</code> 表示网络位占 24 主机位 8
</div>
<div class="concept-item">
<span class="concept-label"> IP </span>
<code>2^(32-24) = 256</code>
</div>
<div class="concept-item">
<span class="concept-label">可用 IP </span>
<code>256 - 3 = 253</code> 减去网络广播网关地址
</div>
</div>
</div>
<div class="calc-section">
<h5>2. 当前配置计算</h5>
<div class="calc-result">
<div class="result-item">
<span class="result-label">VPC 网段</span>
<code class="result-value">{{ vpcCidr }}</code>
</div>
<div class="result-item">
<span class="result-label">子网掩码</span>
<code class="result-value">/{{ subnetMask }}</code>
</div>
<div class="result-item">
<span class="result-label">子网数量</span>
<code class="result-value">{{ subnets.length }} </code>
</div>
<div class="result-item">
<span class="result-label">每个子网 IP </span>
<code class="result-value">{{ ipsPerSubnet }} </code>
</div>
</div>
</div>
</div>
<!-- 最佳实践提示 -->
<div class="tips-panel">
<h4>💡 子网设计最佳实践</h4>
<div class="tips-grid">
<div class="tip-item">
<div class="tip-icon">
🎯
</div>
<div class="tip-content">
<h5>预留足够 IP</h5>
<p>每个子网至少预留 20% IP 作为扩容缓冲</p>
</div>
</div>
<div class="tip-item">
<div class="tip-icon">
🔒
</div>
<div class="tip-content">
<h5>公网私网分离</h5>
<p>核心数据放在私网子网通过 NAT 访问外网</p>
</div>
</div>
<div class="tip-item">
<div class="tip-icon">
🌐
</div>
<div class="tip-content">
<h5> AZ 部署</h5>
<p>同一 VPC 的不同子网放在不同可用区</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const vpcCidr = ref('172.16.0.0/12')
const subnetBits = ref(2)
const showCalculation = ref(false)
const selectedSubnet = ref(null)
const hoverSubnet = ref(null)
const subnetMask = computed(() => {
const baseMask = parseInt(vpcCidr.value.split('/')[1])
return baseMask + subnetBits.value
})
const ipsPerSubnet = computed(() => {
return Math.pow(2, 32 - subnetMask.value)
})
const totalIps = computed(() => {
const mask = parseInt(vpcCidr.value.split('/')[1])
return Math.pow(2, 32 - mask)
})
const subnets = computed(() => {
const baseCidr = vpcCidr.value.split('/')[0]
const octets = baseCidr.split('.').map(Number)
const count = Math.pow(2, subnetBits.value)
const result = []
for (let i = 0; i < count; i++) {
const thirdOctet = octets[2] + Math.floor(i / 256)
const fourthOctet = i % 256
const cidr = `${octets[0]}.${octets[1]}.${thirdOctet}.${fourthOctet}/${subnetMask.value}`
const startIp = `${octets[0]}.${octets[1]}.${thirdOctet}.${fourthOctet}`
const endIp = `${octets[0]}.${octets[1]}.${thirdOctet + Math.floor((ipsPerSubnet.value - 1) / 256)}.${(fourthOctet + ipsPerSubnet.value - 1) % 256}`
result.push({
name: `子网-${String.fromCharCode(65 + i)}`,
cidr,
type: i % 2 === 0 ? 'public' : 'private',
ipCount: ipsPerSubnet.value,
az: `可用区 ${String.fromCharCode(65 + (i % 3))}`,
range: `${startIp} - ${endIp}`,
usableIps: ipsPerSubnet.value - 3
})
}
return result
})
const selectSubnet = (index) => {
selectedSubnet.value = selectedSubnet.value === index ? null : index
}
</script>
<style scoped>
.subnet-design-demo {
padding: 20px;
background: #f5f7fa;
border-radius: 6px;
}
.control-panel {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
margin-bottom: 20px;
padding: 16px;
background: white;
border-radius: 6px;
}
.panel-section {
display: flex;
align-items: center;
gap: 8px;
}
.panel-label {
font-size: 13px;
color: #606266;
font-weight: 500;
}
/* Network Visualization */
.network-visualization {
margin-bottom: 20px;
}
.vpc-block {
background: white;
border-radius: 12px;
padding: 20px;
border: 2px solid #409eff;
}
.vpc-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
}
.vpc-name {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.vpc-cidr {
font-size: 13px;
padding: 4px 8px;
background: #ecf5ff;
color: #409eff;
border-radius: 4px;
font-family: monospace;
}
.vpc-stats {
font-size: 12px;
color: #909399;
}
/* Subnet Grid */
.subnet-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
}
.subnet-cell {
background: #f5f7fa;
border-radius: 6px;
padding: 12px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.subnet-cell:hover {
border-color: #c0c4cc;
transform: translateY(-2px);
}
.subnet-cell.active {
border-color: #409eff;
background: #ecf5ff;
}
.subnet-cell.public {
border-left: 4px solid #409eff;
}
.subnet-cell.private {
border-left: 4px solid #67c23a;
}
.cell-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.cell-type {
font-size: 14px;
}
.cell-name {
font-size: 13px;
font-weight: 600;
color: #303133;
}
.cell-cidr {
font-size: 11px;
color: #606266;
font-family: monospace;
margin-bottom: 8px;
}
.cell-stats {
display: flex;
justify-content: space-between;
align-items: center;
}
.ip-count {
font-size: 11px;
color: #909399;
}
.az-badge {
font-size: 10px;
padding: 2px 6px;
background: #e4e7ed;
border-radius: 10px;
color: #606266;
}
/* Cell Tooltip */
.cell-tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 10px 14px;
border-radius: 6px;
font-size: 12px;
z-index: 10;
margin-bottom: 8px;
white-space: nowrap;
}
.tooltip-row {
display: flex;
justify-content: space-between;
gap: 12px;
margin: 3px 0;
}
/* Calculation Panel */
.calculation-panel {
background: white;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.calculation-panel h4 {
margin: 0 0 16px 0;
color: #303133;
font-size: 16px;
}
.calc-section {
margin-bottom: 20px;
}
.calc-section:last-child {
margin-bottom: 0;
}
.calc-section h5 {
margin: 0 0 12px 0;
color: #606266;
font-size: 14px;
}
.concept-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 12px;
}
.concept-item {
background: #f5f7fa;
padding: 12px;
border-radius: 6px;
font-size: 13px;
color: #606266;
}
.concept-label {
font-weight: 600;
color: #303133;
}
.calc-result {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.result-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: #f5f7fa;
border-radius: 6px;
}
.result-label {
font-size: 13px;
color: #606266;
}
.result-value {
font-size: 13px;
color: #409eff;
font-weight: 600;
font-family: monospace;
}
/* Tips Panel */
.tips-panel {
background: #f0f9eb;
border-radius: 6px;
padding: 20px;
border-left: 4px solid #67c23a;
}
.tips-panel h4 {
margin: 0 0 16px 0;
color: #67c23a;
font-size: 16px;
}
.tips-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
.tip-item {
display: flex;
gap: 12px;
background: white;
padding: 12px;
border-radius: 6px;
}
.tip-icon {
font-size: 24px;
flex-shrink: 0;
}
.tip-content h5 {
margin: 0 0 4px 0;
font-size: 14px;
color: #303133;
}
.tip-content p {
margin: 0;
font-size: 12px;
color: #606266;
line-height: 1.5;
}
@media (max-width: 768px) {
.control-panel {
flex-direction: column;
align-items: stretch;
}
.panel-section {
flex-direction: column;
align-items: flex-start;
}
.subnet-grid {
grid-template-columns: 1fr;
}
}
</style>