528 lines
12 KiB
Vue
528 lines
12 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="subnet-calculator">
|
|||
|
|
<div class="calculator-input">
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label class="input-label">IP 地址</label>
|
|||
|
|
<input
|
|||
|
|
v-model="ipAddress"
|
|||
|
|
type="text"
|
|||
|
|
placeholder="例如: 192.168.1.0"
|
|||
|
|
class="ip-input"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label class="input-label">子网掩码</label>
|
|||
|
|
<select v-model="cidr" class="cidr-select">
|
|||
|
|
<option v-for="n in 32" :key="n" :value="n">/{{ n }}</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<button class="calculate-btn" @click="calculate">计算</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="results" v-if="results">
|
|||
|
|
<div class="result-section">
|
|||
|
|
<div class="section-title">基本信息</div>
|
|||
|
|
<div class="result-grid">
|
|||
|
|
<div class="result-item">
|
|||
|
|
<div class="result-label">网络地址</div>
|
|||
|
|
<div class="result-value">{{ results.network }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="result-item">
|
|||
|
|
<div class="result-label">广播地址</div>
|
|||
|
|
<div class="result-value">{{ results.broadcast }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="result-item">
|
|||
|
|
<div class="result-label">子网掩码</div>
|
|||
|
|
<div class="result-value">{{ results.mask }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="result-item">
|
|||
|
|
<div class="result-label">可用主机数</div>
|
|||
|
|
<div class="result-value">{{ results.hosts }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="result-section">
|
|||
|
|
<div class="section-title">IP 范围</div>
|
|||
|
|
<div class="range-display">
|
|||
|
|
<div class="range-item">
|
|||
|
|
<div class="range-label">起始 IP</div>
|
|||
|
|
<div class="range-value">{{ results.firstHost }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="range-arrow">→</div>
|
|||
|
|
<div class="range-item">
|
|||
|
|
<div class="range-label">结束 IP</div>
|
|||
|
|
<div class="range-value">{{ results.lastHost }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="result-section">
|
|||
|
|
<div class="section-title">二进制表示</div>
|
|||
|
|
<div class="binary-display">
|
|||
|
|
<div class="binary-row">
|
|||
|
|
<div class="binary-label">IP 地址</div>
|
|||
|
|
<div class="binary-value">{{ results.binaryIp }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="binary-row">
|
|||
|
|
<div class="binary-label">子网掩码</div>
|
|||
|
|
<div class="binary-value">{{ results.binaryMask }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="binary-row">
|
|||
|
|
<div class="binary-label">网络地址</div>
|
|||
|
|
<div class="binary-value">{{ results.binaryNetwork }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="result-section">
|
|||
|
|
<div class="section-title">子网类型</div>
|
|||
|
|
<div class="subnet-info">
|
|||
|
|
<div class="info-tag" :class="getSubnetClass(cidr)">
|
|||
|
|
{{ getSubnetType(cidr) }}
|
|||
|
|
</div>
|
|||
|
|
<div class="info-desc">{{ getSubnetDescription(cidr) }}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="example-presets">
|
|||
|
|
<div class="presets-title">常见子网示例</div>
|
|||
|
|
<div class="presets-grid">
|
|||
|
|
<button
|
|||
|
|
v-for="(preset, index) in presets"
|
|||
|
|
:key="index"
|
|||
|
|
class="preset-btn"
|
|||
|
|
@click="applyPreset(preset)"
|
|||
|
|
>
|
|||
|
|
{{ preset.name }}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="info-box">
|
|||
|
|
<div class="info-title">💡 子网划分知识点</div>
|
|||
|
|
<div class="info-content">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<strong>什么是子网?</strong>
|
|||
|
|
将一个大网络分割成更小的网络,提高地址利用率和网络性能。
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<strong>CIDR 表示法</strong>
|
|||
|
|
/24 表示前 24 位是网络位,后 8 位是主机位。
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<strong>常用子网掩码</strong>
|
|||
|
|
<br>
|
|||
|
|
/8 = 255.0.0.0 (A 类网络)
|
|||
|
|
<br>
|
|||
|
|
/16 = 255.255.0.0 (B 类网络)
|
|||
|
|
<br>
|
|||
|
|
/24 = 255.255.255.0 (C 类网络)
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref } from 'vue'
|
|||
|
|
|
|||
|
|
const ipAddress = ref('192.168.1.0')
|
|||
|
|
const cidr = ref(24)
|
|||
|
|
const results = ref(null)
|
|||
|
|
|
|||
|
|
const presets = [
|
|||
|
|
{ name: '小型网络 /24', ip: '192.168.1.0', cidr: 24 },
|
|||
|
|
{ name: '家庭网络 /26', ip: '192.168.1.0', cidr: 26 },
|
|||
|
|
{ name: '大型网络 /16', ip: '192.168.0.0', cidr: 16 },
|
|||
|
|
{ name: '超大型网络 /8', ip: '10.0.0.0', cidr: 8 }
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
const calculate = () => {
|
|||
|
|
const ip = ipAddress.value.split('.').map(Number)
|
|||
|
|
const mask = cidr.value
|
|||
|
|
|
|||
|
|
// 计算子网掩码
|
|||
|
|
const maskBits = Array(32).fill(0).map((_, i) => (i < mask ? 1 : 0))
|
|||
|
|
const maskBytes = []
|
|||
|
|
for (let i = 0; i < 4; i++) {
|
|||
|
|
maskBytes.push(
|
|||
|
|
maskBits.slice(i * 8, (i + 1) * 8).reduce((acc, bit) => acc * 2 + bit, 0)
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算网络地址
|
|||
|
|
const networkBytes = ip.map((byte, i) => byte & maskBytes[i])
|
|||
|
|
|
|||
|
|
// 计算广播地址
|
|||
|
|
const hostBits = 32 - mask
|
|||
|
|
const broadcastBytes = [...networkBytes]
|
|||
|
|
if (hostBits <= 8) {
|
|||
|
|
broadcastBytes[3] |= (1 << hostBits) - 1
|
|||
|
|
} else if (hostBits <= 16) {
|
|||
|
|
broadcastBytes[2] |= ((1 << (hostBits - 8)) - 1)
|
|||
|
|
broadcastBytes[3] = 255
|
|||
|
|
} else if (hostBits <= 24) {
|
|||
|
|
broadcastBytes[1] |= ((1 << (hostBits - 16)) - 1)
|
|||
|
|
broadcastBytes[2] = 255
|
|||
|
|
broadcastBytes[3] = 255
|
|||
|
|
} else {
|
|||
|
|
broadcastBytes[0] |= ((1 << (hostBits - 24)) - 1)
|
|||
|
|
broadcastBytes[1] = 255
|
|||
|
|
broadcastBytes[2] = 255
|
|||
|
|
broadcastBytes[3] = 255
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算可用主机范围
|
|||
|
|
const firstHost = [...broadcastBytes]
|
|||
|
|
firstHost[3] = networkBytes[3] + 1
|
|||
|
|
|
|||
|
|
const lastHost = [...broadcastBytes]
|
|||
|
|
lastHost[3] = broadcastBytes[3] - 1
|
|||
|
|
|
|||
|
|
// 可用主机数
|
|||
|
|
const hosts = Math.pow(2, hostBits) - 2
|
|||
|
|
|
|||
|
|
// 二进制表示
|
|||
|
|
const toBinary = (bytes) =>
|
|||
|
|
bytes.map((b) => b.toString(2).padStart(8, '0')).join('.')
|
|||
|
|
|
|||
|
|
results.value = {
|
|||
|
|
network: networkBytes.join('.'),
|
|||
|
|
broadcast: broadcastBytes.join('.'),
|
|||
|
|
mask: maskBytes.join('.'),
|
|||
|
|
hosts: hosts > 0 ? hosts : 0,
|
|||
|
|
firstHost: firstHost.join('.'),
|
|||
|
|
lastHost: lastHost.join('.'),
|
|||
|
|
binaryIp: toBinary(ip),
|
|||
|
|
binaryMask: toBinary(maskBytes),
|
|||
|
|
binaryNetwork: toBinary(networkBytes)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const applyPreset = (preset) => {
|
|||
|
|
ipAddress.value = preset.ip
|
|||
|
|
cidr.value = preset.cidr
|
|||
|
|
calculate()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getSubnetType = (mask) => {
|
|||
|
|
if (mask <= 8) return 'A 类网络'
|
|||
|
|
if (mask <= 16) return 'B 类网络'
|
|||
|
|
if (mask <= 24) return 'C 类网络'
|
|||
|
|
return '小型子网'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getSubnetClass = (mask) => {
|
|||
|
|
if (mask <= 8) return 'class-a'
|
|||
|
|
if (mask <= 16) return 'class-b'
|
|||
|
|
if (mask <= 24) return 'class-c'
|
|||
|
|
return 'class-small'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getSubnetDescription = (mask) => {
|
|||
|
|
if (mask <= 8) return '超大型网络,适合互联网服务提供商'
|
|||
|
|
if (mask <= 16) return '大型网络,适合公司或机构'
|
|||
|
|
if (mask <= 24) return '标准网络,适合小型企业或家庭'
|
|||
|
|
return '小型子网,适合特定部门或用途'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始计算
|
|||
|
|
calculate()
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.subnet-calculator {
|
|||
|
|
border: 1px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
margin: 20px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.calculator-input {
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
margin-bottom: 25px;
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 15px;
|
|||
|
|
align-items: flex-end;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-group {
|
|||
|
|
flex: 1;
|
|||
|
|
min-width: 200px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-label {
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ip-input,
|
|||
|
|
.cidr-select {
|
|||
|
|
width: 100%;
|
|||
|
|
padding: 10px;
|
|||
|
|
border: 2px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 0.9rem;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ip-input:focus,
|
|||
|
|
.cidr-select:focus {
|
|||
|
|
outline: none;
|
|||
|
|
border-color: var(--vp-c-brand);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.calculate-btn {
|
|||
|
|
padding: 10px 24px;
|
|||
|
|
background: var(--vp-c-brand);
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 0.9rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: background 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.calculate-btn:hover {
|
|||
|
|
background: var(--vp-c-brand-dark);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.results {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 20px;
|
|||
|
|
margin-bottom: 25px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.result-section {
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
padding-bottom: 10px;
|
|||
|
|
border-bottom: 2px solid var(--vp-c-divider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.result-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(2, 1fr);
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.result-grid {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.result-item {
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 15px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.result-label {
|
|||
|
|
font-size: 0.8rem;
|
|||
|
|
color: var(--vp-c-text-3);
|
|||
|
|
margin-bottom: 5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.result-value {
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--vp-c-brand);
|
|||
|
|
font-family: monospace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.range-display {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.range-item {
|
|||
|
|
flex: 1;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 15px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.range-label {
|
|||
|
|
font-size: 0.8rem;
|
|||
|
|
color: var(--vp-c-text-3);
|
|||
|
|
margin-bottom: 5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.range-value {
|
|||
|
|
font-size: 0.95rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--vp-c-brand);
|
|||
|
|
font-family: monospace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.range-arrow {
|
|||
|
|
font-size: 1.5rem;
|
|||
|
|
color: var(--vp-c-text-3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.binary-display {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.binary-row {
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 12px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.binary-label {
|
|||
|
|
width: 100px;
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
color: var(--vp-c-text-3);
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.binary-value {
|
|||
|
|
flex: 1;
|
|||
|
|
font-family: monospace;
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
color: var(--vp-c-brand);
|
|||
|
|
word-break: break-all;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.subnet-info {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-tag {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 6px 16px;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
font-weight: 600;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-tag.class-a {
|
|||
|
|
background: #fee2e2;
|
|||
|
|
color: #dc2626;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-tag.class-b {
|
|||
|
|
background: #fef3c7;
|
|||
|
|
color: #d97706;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-tag.class-c {
|
|||
|
|
background: #dbeafe;
|
|||
|
|
color: #2563eb;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-tag.class-small {
|
|||
|
|
background: #d1fae5;
|
|||
|
|
color: #059669;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-desc {
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.example-presets {
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
margin-bottom: 25px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.presets-title {
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.presets-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(2, 1fr);
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.presets-grid {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preset-btn {
|
|||
|
|
padding: 10px;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
border: 2px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preset-btn:hover {
|
|||
|
|
border-color: var(--vp-c-brand);
|
|||
|
|
color: var(--vp-c-brand);
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-box {
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
border-left: 4px solid var(--vp-c-brand);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-title {
|
|||
|
|
font-size: 1rem;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-content {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item {
|
|||
|
|
font-size: 0.85rem;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
line-height: 1.8;
|
|||
|
|
}
|
|||
|
|
</style>
|