chore: save local history restorations from accidental git restore

This commit is contained in:
sanbuphy
2026-02-23 01:40:56 +08:00
parent 780be69b99
commit 2a0fdd3392
27 changed files with 5971 additions and 2743 deletions
@@ -1,164 +1,82 @@
<template>
<div class="adder-demo">
<div class="demo-header">
<span class="icon">🧮</span>
<span class="title">加法器CPU 怎么做加法</span>
<span class="subtitle">从手算竖式理解"逐位计算"的原理</span>
<div class="demo-label">二进制加法器 输入 015 的两个数观察逐位计算过程</div>
<div class="control-row">
<label class="input-group">
<span class="input-label">A</span>
<input v-model.number="inputA" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">+</span>
<label class="input-group">
<span class="input-label">B</span>
<input v-model.number="inputB" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">=</span>
<span class="result-num">{{ resultDec }}</span>
</div>
<div class="intro-section">
<div class="intro-title">🎯 先看十进制竖式理解"逐位计算"</div>
<div class="decimal-demo">
<div class="decimal-column">
<div class="decimal-row label-row">被加数</div>
<div class="decimal-row num-row">
<span class="d-digit">{{ decimalA }}</span>
</div>
</div>
<div class="decimal-column op-col">
<div class="decimal-row label-row">+</div>
<div class="decimal-row num-row">
<span class="d-digit">{{ decimalB }}</span>
</div>
</div>
<div class="decimal-column">
<div class="decimal-row label-row">结果</div>
<div class="decimal-row num-row result">
<span class="d-digit">{{ decimalA + decimalB }}</span>
</div>
</div>
<div class="binary-display">
<div class="binary-row">
<span class="binary-label">A</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsA" :key="'a'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ clampedA }}</span>
</div>
<div class="intro-hint">
<span class="icon">💡</span>
<span>手算时我们从<strong>个位往高位</strong>一位一位算<strong>逢十进一</strong>CPU 做加法也一样只是它只认识 0 1所以要<strong>逢二进一</strong></span>
<div class="binary-row">
<span class="binary-label">B</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsB" :key="'b'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ clampedB }}</span>
</div>
<div class="binary-row sum-row">
<span class="binary-label">结果</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsSum" :key="'s'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ fourBitResult }}</span>
</div>
<div class="bit-labels">
<span v-for="i in 4" :key="i" class="bit-label">{{ 4 - i }}</span>
</div>
</div>
<div class="concept-section">
<div class="concept-title">📚 核心概念</div>
<div class="concepts-grid">
<div class="concept-card half-adder">
<div class="concept-name">半加器</div>
<div class="concept-simple">只算 A + B</div>
<div class="concept-detail">
<p>最右边一位用因为<strong>没有进位进来</strong></p>
<p class="formula">输入AB 输出(S)进位(C)</p>
</div>
</div>
<div class="concept-card full-adder">
<div class="concept-name">全加器</div>
<div class="concept-simple"> A + B + 进位</div>
<div class="concept-detail">
<p>其他位用因为<strong>要加上一位的进位</strong></p>
<p class="formula">输入ABCin 输出(S)进位(Cout)</p>
</div>
</div>
</div>
</div>
<div class="demo-section">
<div class="demo-title">🎮 动手试试二进制加法</div>
<div class="control-row">
<label class="input-group">
<span class="input-label">A被加数</span>
<input v-model.number="inputA" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">+</span>
<label class="input-group">
<span class="input-label">B加数</span>
<input v-model.number="inputB" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">=</span>
<span class="result-num">{{ resultDec }}</span>
</div>
<div class="binary-display">
<div class="binary-row">
<span class="binary-label">A</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsA" :key="'a'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
<div class="stages-row">
<div
v-for="(stage, idx) in stages" :key="idx"
class="stage-card"
:class="{ active: activeBit === stage.bitPos }"
@mouseenter="activeBit = stage.bitPos"
@mouseleave="activeBit = null"
>
<div class="stage-head">
<span class="stage-pos">{{ stage.bitPos }}</span>
<span class="stage-type" :class="stage.carryIn !== null ? 'full' : 'half'">
{{ stage.carryIn !== null ? '全加器' : '半加器' }}
</span>
<span class="binary-dec">= {{ inputA }}</span>
</div>
<div class="binary-row">
<span class="binary-label">B</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsB" :key="'b'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ inputB }}</span>
<div class="stage-io">
<span class="io-item"><span class="io-tag a">A</span>{{ stage.a }}</span>
<span class="io-item"><span class="io-tag b">B</span>{{ stage.b }}</span>
<span v-if="stage.carryIn !== null" class="io-item"><span class="io-tag cin">Cin</span>{{ stage.carryIn }}</span>
</div>
<div class="binary-row result-row">
<span class="binary-label">结果</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsSum" :key="'s'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ fourBitResult }}</span>
</div>
<div class="bit-labels">
<span v-for="i in 4" :key="i" class="bit-label">{{ 4 - i }}</span>
</div>
</div>
<div class="stages-row">
<div
v-for="(stage, idx) in stages"
:key="idx"
class="stage-card"
:class="{ active: activeBit === stage.bitPos }"
@mouseenter="activeBit = stage.bitPos"
@mouseleave="activeBit = null"
>
<div class="stage-header">
<span class="stage-pos">{{ stage.bitPos }}</span>
<span class="stage-type" :class="stage.carryIn !== null ? 'full' : 'half'">
{{ stage.carryIn !== null ? '全加器' : '半加器' }}
</span>
</div>
<div class="stage-io">
<div class="io-line">
<span class="io-tag a">A</span>
<span class="io-val">{{ stage.a }}</span>
</div>
<div class="io-line">
<span class="io-tag b">B</span>
<span class="io-val">{{ stage.b }}</span>
</div>
<div v-if="stage.carryIn !== null" class="io-line">
<span class="io-tag cin">Cin</span>
<span class="io-val">{{ stage.carryIn }}</span>
</div>
</div>
<div class="stage-divider"></div>
<div class="stage-io">
<div class="io-line">
<span class="io-tag s">S</span>
<span class="io-val sum">{{ stage.sum }}</span>
</div>
<div class="io-line">
<span class="io-tag cout">Cout</span>
<span class="io-val">{{ stage.carryOut }}</span>
</div>
</div>
<div v-if="idx < 3" class="carry-arrow" :class="{ hasCarry: stage.carryOut }">
{{ stage.carryOut ? ' 进位' : '' }}
</div>
<div class="stage-divider"></div>
<div class="stage-io">
<span class="io-item"><span class="io-tag s">S</span><strong>{{ stage.sum }}</strong></span>
<span class="io-item"><span class="io-tag cout">C</span>{{ stage.carryOut }}</span>
</div>
</div>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>每位加法器接收 AB 和上一位的进位输出本位的和与传给下一位的进位就像手算竖式"逢二进一"只是用电路自动完成
</div>
<div class="demo-caption">鼠标悬停某一位查看该位加法器的输入 / 输出 · 就像手算竖式"逢二进一"</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const decimalA = 35
const decimalB = 47
const inputA = ref(3)
const inputB = ref(2)
const activeBit = ref(null)
@@ -191,14 +109,7 @@ const stages = computed(() => {
sum = (a ^ b) ^ carryIn
carryOut = (a & b) | (carryIn & (a ^ b))
}
result.push({
bitPos: i,
a,
b,
carryIn: carryIn === null ? null : carryIn,
sum,
carryOut
})
result.push({ bitPos: i, a, b, carryIn: carryIn === null ? null : carryIn, sum, carryOut })
carryIn = carryOut
}
return result
@@ -215,7 +126,7 @@ const fourBitResult = computed(() =>
const overflow = computed(() => clampedA.value + clampedB.value > 15)
const resultDec = computed(() =>
overflow.value ? `${fourBitResult.value}4位溢出)` : String(fourBitResult.value)
overflow.value ? `${fourBitResult.value}(溢出)` : String(fourBitResult.value)
)
</script>
@@ -224,164 +135,25 @@ const resultDec = computed(() =>
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.25rem; }
.intro-section {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.intro-title {
.demo-label {
font-size: 0.78rem;
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.decimal-demo {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.decimal-column {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.decimal-column.op-col {
min-width: 2rem;
text-align: center;
}
.decimal-row {
font-size: 0.85rem;
}
.decimal-row.label-row {
color: var(--vp-c-text-3);
font-size: 0.75rem;
}
.decimal-row.num-row {
font-family: monospace;
font-size: 1.1rem;
font-weight: bold;
}
.decimal-row.num-row.result {
color: var(--vp-c-brand-1);
}
.d-digit {
display: inline-block;
min-width: 1.5rem;
text-align: center;
}
.intro-hint {
display: flex;
align-items: flex-start;
gap: 0.35rem;
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.intro-hint .icon {
flex-shrink: 0;
}
.concept-section {
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.concept-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.concepts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.concept-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.6rem;
}
.concept-name {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.15rem;
}
.concept-simple {
font-size: 0.8rem;
color: var(--vp-c-brand-1);
font-weight: 600;
margin-bottom: 0.25rem;
}
.concept-detail {
font-size: 0.75rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.concept-detail p {
margin: 0;
}
.concept-detail .formula {
margin-top: 0.2rem;
font-family: monospace;
color: var(--vp-c-text-3);
}
.half-adder .concept-name { color: var(--vp-c-brand-1); }
.full-adder .concept-name { color: #8b5cf6; }
.demo-section {
margin-bottom: 0.5rem;
}
.demo-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
/* ── controls ── */
.control-row {
display: flex;
align-items: center;
gap: 0.4rem;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.5rem;
margin-bottom: 0.6rem;
}
.input-group {
@@ -391,67 +163,70 @@ const resultDec = computed(() =>
}
.input-label {
font-size: 0.8rem;
font-size: 0.82rem;
font-weight: 600;
color: var(--vp-c-text-2);
}
.num-input {
width: 3rem;
padding: 0.2rem 0.35rem;
width: 3.2rem;
padding: 0.25rem 0.4rem;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
font-size: 0.85rem;
font-size: 0.9rem;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
.op-sign {
font-weight: bold;
color: var(--vp-c-text-2);
color: var(--vp-c-text-3);
}
.result-num {
font-weight: bold;
color: var(--vp-c-brand-1);
font-size: 0.95rem;
font-size: 1rem;
}
/* ── binary ── */
.binary-display {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.5rem 0.75rem;
margin-bottom: 0.5rem;
margin-bottom: 0.6rem;
}
.binary-row {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.2rem;
margin-bottom: 0.15rem;
font-size: 0.85rem;
}
.binary-label {
color: var(--vp-c-text-2);
min-width: 2.5rem;
font-weight: 600;
}
.binary-bits {
display: flex;
gap: 0.2rem;
font-family: monospace;
font-family: 'JetBrains Mono', monospace;
}
.bit {
display: inline-block;
min-width: 1.2rem;
min-width: 1.3rem;
text-align: center;
padding: 0.1rem 0.15rem;
border-radius: 3px;
transition: all 0.15s ease;
transition: all 0.15s;
}
.bit.highlight {
.bit.hl {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
font-weight: bold;
@@ -459,11 +234,11 @@ const resultDec = computed(() =>
.binary-dec {
color: var(--vp-c-text-3);
font-size: 0.8rem;
font-size: 0.78rem;
margin-left: 0.25rem;
}
.result-row .binary-bits {
.sum-row .binary-bits {
font-weight: bold;
color: var(--vp-c-brand-1);
}
@@ -472,30 +247,31 @@ const resultDec = computed(() =>
display: flex;
gap: 0.2rem;
margin-left: 3rem;
margin-top: 0.15rem;
margin-top: 0.1rem;
}
.bit-label {
min-width: 1.2rem;
min-width: 1.3rem;
text-align: center;
font-size: 0.65rem;
font-size: 0.6rem;
color: var(--vp-c-text-3);
}
/* ── stages ── */
.stages-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.4rem;
margin-bottom: 0.5rem;
}
.stage-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.4rem;
padding: 0.45rem;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
transition: all 0.15s;
}
.stage-card.active {
@@ -503,25 +279,25 @@ const resultDec = computed(() =>
box-shadow: 0 0 0 1px var(--vp-c-brand-1);
}
.stage-header {
.stage-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.25rem;
padding-bottom: 0.2rem;
margin-bottom: 0.2rem;
padding-bottom: 0.15rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.stage-pos {
font-size: 0.7rem;
font-size: 0.68rem;
font-weight: bold;
color: var(--vp-c-text-2);
}
.stage-type {
font-size: 0.65rem;
font-size: 0.6rem;
font-weight: bold;
padding: 0.1rem 0.25rem;
padding: 0.08rem 0.25rem;
border-radius: 3px;
}
@@ -538,22 +314,24 @@ const resultDec = computed(() =>
.stage-io {
display: flex;
flex-direction: column;
gap: 0.15rem;
gap: 0.1rem;
}
.io-line {
.io-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.2rem;
gap: 0.25rem;
font-family: 'JetBrains Mono', monospace;
font-size: 0.78rem;
}
.io-tag {
font-size: 0.6rem;
font-size: 0.55rem;
font-weight: bold;
padding: 0.05rem 0.2rem;
padding: 0.04rem 0.18rem;
border-radius: 2px;
color: white;
font-family: system-ui;
}
.io-tag.a { background: var(--vp-c-brand-1); }
@@ -562,59 +340,21 @@ const resultDec = computed(() =>
.io-tag.s { background: var(--vp-c-green-1, #16a34a); }
.io-tag.cout { background: #d97706; }
.io-val {
font-family: monospace;
font-size: 0.8rem;
font-weight: bold;
}
.io-val.sum {
color: var(--vp-c-green-1, #16a34a);
}
.stage-divider {
height: 1px;
background: var(--vp-c-divider);
margin: 0.2rem 0;
}
.carry-arrow {
position: absolute;
right: -0.5rem;
top: 50%;
transform: translateY(-50%);
font-size: 0.6rem;
color: #d97706;
white-space: nowrap;
}
.carry-arrow.hasCarry {
font-weight: bold;
}
.info-box {
display: flex;
align-items: flex-start;
gap: 0.35rem;
background: var(--vp-c-bg-alt);
padding: 0.6rem 0.75rem;
border-radius: 6px;
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.info-box .icon {
flex-shrink: 0;
.demo-caption {
font-size: 0.72rem;
color: var(--vp-c-text-3);
text-align: center;
}
@media (max-width: 600px) {
.stages-row {
grid-template-columns: repeat(2, 1fr);
}
.concepts-grid {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,380 @@
<template>
<div class="cpu-architecture-demo">
<div class="demo-label">
CPU 核心组件与指令执行周期演示 点击"时钟脉冲"执行一个指令周期
</div>
<div class="cpu-container">
<div class="cpu-frame">
<h3 class="cpu-title">CPU (中央处理器)</h3>
<div class="components-grid">
<!-- Control Unit -->
<div
class="cu-box component"
:class="{ active: currentStage === 1 || currentStage === 2 }"
>
<div class="comp-title">控制单元 (CU)</div>
<div class="comp-state">{{ cuState }}</div>
</div>
<!-- ALU -->
<div
class="alu-box component"
:class="{ active: currentStage === 3 }"
>
<div class="comp-title">算术逻辑单元 (ALU)</div>
<div class="comp-state">{{ aluState }}</div>
</div>
<!-- Registers -->
<div
class="reg-box component"
:class="{ active: currentStage === 4 }"
>
<div class="comp-title">寄存器组</div>
<div class="reg-list">
<span class="reg">R0: {{ r0 }}</span>
<span class="reg">R1: {{ r1 }}</span>
<span class="reg">PC: {{ pc }}</span>
</div>
</div>
</div>
</div>
<!-- Memory -->
<div
class="mem-frame component"
:class="{ active: currentStage === 1 || currentStage === 4 }"
>
<h3 class="cpu-title">内存 (Memory)</h3>
<div class="mem-list">
<div class="mem-loc" :class="{ 'hl-mem': pc === 10 }">
<span class="addr">M[10]</span> 取指LOAD R0, #5
</div>
<div class="mem-loc" :class="{ 'hl-mem': pc === 11 }">
<span class="addr">M[11]</span> 译码ADD R1, R0
</div>
<div class="mem-loc" :class="{ 'hl-mem': pc === 12 }">
<span class="addr">M[12]</span> 执行ALU 计算
</div>
<div class="mem-loc" :class="{ 'hl-mem': pc === 13 }">
<span class="addr">M[13]</span> 写回将结果保存
</div>
</div>
</div>
</div>
<!-- Stages progress -->
<div class="pipeline">
<div class="stage" :class="{ active: currentStage === 1 }">
<span class="step-num">1. Fetch</span>
<span class="step-desc">取指</span>
</div>
<div class="stage" :class="{ active: currentStage === 2 }">
<span class="step-num">2. Decode</span>
<span class="step-desc">译码</span>
</div>
<div class="stage" :class="{ active: currentStage === 3 }">
<span class="step-num">3. Execute</span>
<span class="step-desc">执行</span>
</div>
<div class="stage" :class="{ active: currentStage === 4 }">
<span class="step-num">4. WriteBack</span>
<span class="step-desc">写回</span>
</div>
</div>
<div class="controls">
<button class="clock-btn" @click="nextStage">
<span class="clock-icon"></span> 给一个时钟脉冲 (Next Stage)
</button>
<button class="reset-btn" @click="reset">重置</button>
</div>
<div class="logic-explain">
<p>
当前阶段状态<strong>{{ statusMsg }}</strong>
</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const currentStage = ref(0) // 0 = Idle, 1 = Fetch, 2 = Decode, 3 = Execute, 4 = Writeback
const cycleCount = ref(0)
const pc = ref(10)
const r0 = ref(0)
const r1 = ref(0)
const cuState = ref('等待时钟信号...')
const aluState = ref('空闲')
const instructions = [
'LOAD R0, #5',
'LOAD R1, #3',
'ADD R0, R1',
'STORE M[14], R0'
]
const statusMsg = computed(() => {
if (currentStage.value === 0)
return '系统启动,等待接收时钟脉冲开始运行程序。'
if (currentStage.value === 1)
return `CPU 内部的控制单元根据程序计数器 (PC=${pc.value}),从内存取出当前指令。`
if (currentStage.value === 2)
return `控制单元翻译指令为硬件控制信号:准备执行操作。`
if (currentStage.value === 3)
return `ALU 进行计算或控制流转移,当前在处理实际数据...`
if (currentStage.value === 4)
return `将运算结果写入寄存器组或写回内存,更新程序计数器(PC)。`
return ''
})
function nextStage() {
if (currentStage.value === 0 || currentStage.value === 4) {
currentStage.value = 1
cuState.value = `取指: 读取指令`
aluState.value = '空闲'
if (currentStage.value === 4) pc.value++
} else if (currentStage.value === 1) {
currentStage.value = 2
cuState.value = `译码: 准备相关电路`
} else if (currentStage.value === 2) {
currentStage.value = 3
cuState.value = '等待 ALU 结果'
aluState.value = '计算进行中...'
} else if (currentStage.value === 3) {
currentStage.value = 4
cuState.value = '完成'
aluState.value = '结果输出'
// Fake logic update
if (cycleCount.value === 0) r0.value = 5
if (cycleCount.value === 1) r1.value = 3
if (cycleCount.value === 2) r0.value = r0.value + r1.value
cycleCount.value++
if (pc.value >= 13) {
pc.value = 9
}
}
}
function reset() {
currentStage.value = 0
cycleCount.value = 0
pc.value = 10
r0.value = 0
r1.value = 0
cuState.value = '等待时钟信号...'
aluState.value = '空闲'
}
</script>
<style scoped>
.cpu-architecture-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: 0.75rem;
letter-spacing: 0.2px;
}
.cpu-container {
display: flex;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.cpu-frame {
flex: 2;
border: 2px dashed var(--vp-c-brand-1);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg);
}
.mem-frame {
flex: 1;
border: 2px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg-alt);
}
.cpu-title {
margin: 0 0 1rem 0;
font-size: 0.95rem;
font-weight: bold;
color: var(--vp-c-brand-1);
text-align: center;
}
.components-grid {
display: flex;
flex-direction: column;
gap: 1rem;
}
.component {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.8rem;
border-radius: 6px;
transition: all 0.3s ease;
}
.component.active {
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
.comp-title {
font-size: 0.8rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.3rem;
}
.comp-state {
font-size: 0.75rem;
color: var(--vp-c-text-1);
font-family: monospace;
}
.reg-list {
display: flex;
gap: 0.5rem;
}
.reg {
font-family: monospace;
font-size: 0.75rem;
background: var(--vp-c-bg);
padding: 0.2rem 0.4rem;
border-radius: 3px;
border: 1px solid var(--vp-c-divider);
}
.mem-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
font-family: monospace;
font-size: 0.75rem;
}
.mem-loc {
padding: 0.3rem 0.5rem;
background: var(--vp-c-bg);
border-radius: 4px;
border: 1px solid var(--vp-c-divider);
}
.mem-loc.hl-mem {
background: #fef08a;
color: #a16207;
border-color: #a16207;
font-weight: bold;
}
.addr {
color: var(--vp-c-text-3);
margin-right: 0.5rem;
}
/* Pipeline Stages */
.pipeline {
display: flex;
justify-content: space-between;
margin-bottom: 1.5rem;
background: var(--vp-c-bg);
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
overflow: hidden;
}
.stage {
flex: 1;
text-align: center;
padding: 0.5rem 0;
border-right: 1px solid var(--vp-c-divider);
transition: all 0.2s;
}
.stage:last-child {
border-right: none;
}
.stage.active {
background: var(--vp-c-brand-1);
color: white;
}
.step-num {
display: block;
font-size: 0.7rem;
font-weight: 600;
margin-bottom: 0.1rem;
}
.step-desc {
display: block;
font-size: 0.8rem;
}
/* Controls */
.controls {
display: flex;
gap: 1rem;
justify-content: center;
}
.clock-btn,
.reset-btn {
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.85rem;
font-weight: bold;
cursor: pointer;
}
.clock-btn {
background: var(--vp-c-brand-1);
color: white;
border: none;
}
.clock-btn:hover {
background: var(--vp-c-brand-2);
}
.reset-btn {
background: transparent;
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.logic-explain {
margin-top: 1rem;
padding: 0.8rem;
background: var(--vp-c-bg);
border-radius: 6px;
font-size: 0.85rem;
text-align: center;
color: var(--vp-c-text-2);
}
@media (max-width: 600px) {
.cpu-container {
flex-direction: column;
}
}
</style>
@@ -1,303 +1,314 @@
<template>
<div class="filesystem-demo">
<div class="demo-header">
<span class="title">文件系统数据的"档案柜"</span>
<span class="subtitle">操作系统如何组织和管理文件</span>
</div>
<div class="demo-content">
<div class="fs-tree">
<div class="tree-header">
<span class="header-icon">📂</span>
<span>目录结构</span>
</div>
<div class="tree-content">
<div
v-for="item in fileTree"
:key="item.path"
class="tree-item"
:class="{ selected: selectedItem === item.path }"
:style="{ paddingLeft: (item.level * 12) + 'px' }"
@click="selectItem(item)"
>
<span class="item-icon">{{ item.icon }}</span>
<span class="item-name">{{ item.name }}</span>
</div>
</div>
</div>
<div class="fs-detail">
<div class="detail-header">
<span class="detail-icon">{{ selectedItemInfo?.icon }}</span>
<span class="detail-name">{{ selectedItemInfo?.name }}</span>
<div class="demo-wrapper">
<!-- 文件树逻辑视角 -->
<div class="logical-view">
<div class="view-title">
<span>📁 你的视角 (文件系统)</span>
<span class="subtitle">漂亮整洁的目录树</span>
</div>
<div
v-if="selectedItemInfo"
class="detail-info"
>
<div class="info-row">
<span class="info-label">类型</span>
<span class="info-value">{{ selectedItemInfo.type }}</span>
<div class="file-tree">
<div class="tree-node folder expanded">
<span class="icon">💾</span> D盘 (根目录)
</div>
<div class="info-row">
<span class="info-label">路径</span>
<span class="info-value">{{ selectedItemInfo.path }}</span>
</div>
<div
v-if="selectedItemInfo.type === '文件'"
class="info-row"
>
<span class="info-label">大小</span>
<span class="info-value">{{ selectedItemInfo.size }}</span>
</div>
<div class="info-row">
<span class="info-label">权限</span>
<span class="info-value">{{ selectedItemInfo.permission }}</span>
</div>
</div>
<div
v-if="selectedItemInfo?.type === '文件'"
class="inode-info"
>
<div class="inode-title">
inode 信息
</div>
<div class="inode-visual">
<div class="inode-block">
<span class="inode-label">inode 编号</span>
<span class="inode-value">{{ selectedItemInfo.inode }}</span>
<div class="tree-children">
<div class="tree-node folder expanded">
<span class="icon">📂</span> 照片
</div>
<div class="inode-block">
<span class="inode-label">数据块</span>
<div class="data-blocks">
<span
v-for="b in selectedItemInfo.blocks"
:key="b"
class="block"
>{{ b }}</span>
<div class="tree-children">
<div
class="tree-node file"
:class="{ active: activeFile === 'pet' }"
@click="selectFile('pet')"
>
<span class="icon">🖼</span> 宠物.jpg
<span class="size-badge">3 </span>
</div>
<div
class="tree-node file"
:class="{ active: activeFile === 'vacation' }"
@click="selectFile('vacation')"
>
<span class="icon">🖼</span> 旅游.png
<span class="size-badge">2 </span>
</div>
</div>
<div class="tree-node folder expanded">
<span class="icon">📂</span> 工作
</div>
<div class="tree-children">
<div
class="tree-node file"
:class="{ active: activeFile === 'doc' }"
@click="selectFile('doc')"
>
<span class="icon">📄</span> 总结.docx
<span class="size-badge">4 </span>
</div>
</div>
</div>
</div>
</div>
<!-- 翻译官动画 -->
<div class="translator">
<div class="arrow"></div>
<div class="badge">文件系统账本<br/>(inode表)</div>
<div class="arrow"></div>
</div>
<!-- 磁盘块物理视角 -->
<div class="physical-view">
<div class="view-title">
<span>🖨 硬盘的视角 (物理存储)</span>
<span class="subtitle">无序零散的数据块</span>
</div>
<div class="disk-grid">
<div
v-for="block in 24"
:key="block"
class="disk-block"
:class="[
getBlockOwner(block),
{ active: isBlockActive(block) }
]"
>
{{ block }}
</div>
</div>
</div>
</div>
<div class="explanation-box" v-if="activeFile">
<span v-if="activeFile === 'pet'">
💡 宠物.jpg 其实被切碎分别放在了第 3814 文件系统帮你做好了翻译你只需双击它
</span>
<span v-if="activeFile === 'vacation'">
💡 旅游.png 放在了第 56
</span>
<span v-if="activeFile === 'doc'">
💡 总结.docx 被分散存放在 10111822 如果没有文件系统你得自己背下这些数字才能打开文件
</span>
</div>
<div class="explanation-box default" v-else>
试着点击左侧的文件看看它们在硬盘里到底长什么样
</div>
<div class="info-box">
<strong>核心思想</strong>文件系统用"目录树"组织文件"inode"记录文件元数据文件名只是给人看的系统通过 inode 编号找到真正的数据
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref } from 'vue'
const selectedItem = ref('/home')
const activeFile = ref(null)
const fileTree = ref([
{ name: '/', path: '/', icon: '📁', level: 0, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'home', path: '/home', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'user', path: '/home/user', icon: '📁', level: 2, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'documents', path: '/home/user/documents', icon: '📁', level: 3, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'report.pdf', path: '/home/user/documents/report.pdf', icon: '📄', level: 4, type: '文件', size: '2.5MB', permission: 'rw-r--r--', inode: 12345, blocks: ['块1', '块2', '块3'] },
{ name: 'photos', path: '/home/user/photos', icon: '📁', level: 3, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'vacation.jpg', path: '/home/user/photos/vacation.jpg', icon: '🖼️', level: 4, type: '文件', size: '4.2MB', permission: 'rw-r--r--', inode: 12346, blocks: ['块4', '块5', '块6', '块7'] },
{ name: 'etc', path: '/etc', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'config.yml', path: '/etc/config.yml', icon: '⚙️', level: 2, type: '文件', size: '1.2KB', permission: 'rw-r--r--', inode: 10001, blocks: ['块8'] },
{ name: 'var', path: '/var', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'log', path: '/var/log', icon: '📁', level: 2, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'system.log', path: '/var/log/system.log', icon: '📝', level: 3, type: '文件', size: '128MB', permission: 'rw-r-----', inode: 20001, blocks: ['块9', '块10', '...'] }
])
// 映射关系伪造
const fileMap = {
pet: [3, 8, 14],
vacation: [5, 6],
doc: [10, 11, 18, 22]
}
const selectedItemInfo = computed(() => {
return fileTree.value.find(item => item.path === selectedItem.value)
})
const selectFile = (file) => {
activeFile.value = file
}
const selectItem = (item) => {
selectedItem.value = item.path
const getBlockOwner = (block) => {
for (const [key, blocks] of Object.entries(fileMap)) {
if (blocks.includes(block)) return `owner-${key}`
}
return 'empty'
}
const isBlockActive = (block) => {
if (!activeFile.value) return false
return fileMap[activeFile.value].includes(block)
}
</script>
<style scoped>
.filesystem-demo {
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.demo-content {
.demo-wrapper {
display: flex;
align-items: stretch;
gap: 1rem;
flex-wrap: wrap;
margin-bottom: 1.5rem;
}
.fs-tree {
@media (max-width: 768px) {
.demo-wrapper {
flex-direction: column;
}
.translator {
transform: rotate(90deg);
margin: 1rem 0;
}
}
.logical-view, .physical-view {
flex: 1;
min-width: 250px;
background: var(--vp-c-bg);
border-radius: 6px;
overflow: hidden;
}
.tree-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
font-weight: bold;
font-size: 0.85rem;
border-radius: 10px;
padding: 1rem;
border: 1px solid var(--vp-c-divider);
}
.tree-content {
max-height: 280px;
overflow-y: auto;
}
.tree-item {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.35rem 0.5rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.85rem;
}
.tree-item:hover {
background: var(--vp-c-bg-soft);
}
.tree-item.selected {
background: var(--vp-c-brand-soft);
}
.item-icon {
font-size: 0.9rem;
}
.fs-detail {
flex: 1;
min-width: 250px;
}
.detail-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
margin-bottom: 0.75rem;
}
.detail-icon {
font-size: 1.5rem;
}
.detail-name {
font-weight: bold;
font-size: 1rem;
}
.detail-info {
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
margin-bottom: 0.75rem;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 0.25rem 0;
font-size: 0.85rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
color: var(--vp-c-text-2);
}
.info-value {
font-weight: 500;
}
.inode-info {
background: var(--vp-c-bg-alt);
padding: 0.5rem;
border-radius: 4px;
}
.inode-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
.inode-visual {
.view-title {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px dashed var(--vp-c-divider);
}
.inode-block {
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
}
.inode-label {
display: block;
font-size: 0.75rem;
color: var(--vp-c-text-2);
margin-bottom: 0.25rem;
}
.inode-value {
.view-title span {
font-weight: bold;
font-size: 0.95rem;
}
.view-title .subtitle {
font-size: 0.75rem;
color: var(--vp-c-text-3);
font-weight: normal;
margin-top: 0.2rem;
}
/* File Tree Styles */
.file-tree {
font-size: 0.9rem;
}
.data-blocks {
display: flex;
gap: 0.25rem;
flex-wrap: wrap;
}
.block {
padding: 0.15rem 0.4rem;
background: var(--vp-c-brand-soft);
border-radius: 3px;
font-size: 0.75rem;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
.tree-node {
padding: 0.4rem 0.5rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.tree-node:hover {
background: var(--vp-c-bg-mute);
}
.tree-node.file.active {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
font-weight: bold;
}
.tree-children {
padding-left: 1.5rem;
border-left: 1px dashed var(--vp-c-divider);
margin-left: 0.6rem;
}
.size-badge {
margin-left: auto;
font-size: 0.7rem;
background: var(--vp-c-bg-mute);
padding: 0.1rem 0.4rem;
border-radius: 4px;
color: var(--vp-c-text-2);
}
.tree-node.active .size-badge {
background: var(--vp-c-brand-1);
color: white;
}
/* Translator */
.translator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.translator .badge {
background: var(--vp-c-brand-1);
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
font-size: 0.8rem;
font-weight: bold;
text-align: center;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
}
.arrow {
width: 2px;
height: 20px;
background: var(--vp-c-divider);
position: relative;
}
.arrow::after {
content: '';
position: absolute;
bottom: -4px;
left: -4px;
border-width: 5px;
border-style: solid;
border-color: var(--vp-c-divider) transparent transparent transparent;
}
/* Disk Grid */
.disk-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.4rem;
}
.disk-block {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: var(--vp-c-text-3);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.disk-block.owner-pet { background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.3); }
.disk-block.owner-vacation { background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.3); }
.disk-block.owner-doc { background: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.3); }
.disk-block.active {
transform: scale(1.1);
color: white;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
z-index: 2;
}
.disk-block.owner-pet.active { background: var(--vp-c-success-1); border-color: var(--vp-c-success-1); }
.disk-block.owner-vacation.active { background: var(--vp-c-brand-1); border-color: var(--vp-c-brand-1); }
.disk-block.owner-doc.active { background: var(--vp-c-warning-1); border-color: var(--vp-c-warning-1); }
.explanation-box {
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
animation: fadeIn 0.3s;
}
.explanation-box.default {
background: var(--vp-c-bg-alt);
border-left-color: var(--vp-c-text-3);
color: var(--vp-c-text-2);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
</style>
@@ -0,0 +1,369 @@
<template>
<div class="functional-unit-demo">
<div class="demo-label">
常见功能单元 切换不同模块查看其实际工作原理
</div>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
class="tab-btn"
:class="{ active: currentTab === tab.id }"
@click="currentTab = tab.id"
>
{{ tab.name }}
</button>
</div>
<div class="demo-content">
<!-- MUX Demo -->
<div v-if="currentTab === 'mux'" class="demo-panel">
<div class="panel-desc">
<strong>多路选择器 (MUX)</strong>像铁路道岔一样根据"选择信号"决定让哪一路数据通过
</div>
<div class="mux-container">
<div class="inputs">
<div class="input-line">
<span class="label">数据 0 (D0)</span>
<button
class="toggle-btn"
:class="{ on: muxD0 }"
@click="muxD0 = !muxD0"
>
{{ muxD0 ? '1' : '0' }}
</button>
</div>
<div class="input-line">
<span class="label">数据 1 (D1)</span>
<button
class="toggle-btn"
:class="{ on: muxD1 }"
@click="muxD1 = !muxD1"
>
{{ muxD1 ? '1' : '0' }}
</button>
</div>
</div>
<div class="mux-chip">
<div class="chip-body">MUX</div>
<div class="select-pin">
<span class="label">选择 (Sel)</span>
<button
class="select-btn"
:class="{ on: muxSel }"
@click="muxSel = !muxSel"
>
{{ muxSel ? '1' : '0' }}
</button>
</div>
</div>
<div class="outputs">
<div class="output-line" :class="{ active: muxResult }">
<span class="label">输出 (Out)</span>
<span class="out-val">{{ muxResult ? '1' : '0' }}</span>
</div>
</div>
</div>
<div class="logic-explain">
<p>
当前选择信号为 {{ muxSel ? '1' : '0' }}因此输出等于 数据
{{ muxSel ? '1 (D1)' : '0 (D0)' }} 的值<strong>{{
muxResult ? '1' : '0'
}}</strong>
</p>
</div>
</div>
<!-- Decoder Demo -->
<div v-if="currentTab === 'decoder'" class="demo-panel">
<div class="panel-desc">
<strong>译码器 (Decoder)</strong>将二进制输入转换为特定输出线的激活信号例如 2位输入可以激活
4根输出线中的一根
</div>
<div class="decoder-container">
<div class="inputs vertical">
<div class="input-line">
<button
class="toggle-btn"
:class="{ on: decA1 }"
@click="decA1 = !decA1"
>
{{ decA1 ? '1' : '0' }}
</button>
<span class="label">A1 (高位)</span>
</div>
<div class="input-line">
<button
class="toggle-btn"
:class="{ on: decA0 }"
@click="decA0 = !decA0"
>
{{ decA0 ? '1' : '0' }}
</button>
<span class="label">A0 (低位)</span>
</div>
</div>
<div class="decoder-chip">
<div class="chip-body">2-to-4<br />译码器</div>
</div>
<div class="outputs vertical-out">
<div class="output-line" :class="{ active: decResult === 0 }">
<span class="out-val">{{ decResult === 0 ? '1' : '0' }}</span>
<span class="label">Y0 (当输入 00 )</span>
</div>
<div class="output-line" :class="{ active: decResult === 1 }">
<span class="out-val">{{ decResult === 1 ? '1' : '0' }}</span>
<span class="label">Y1 (当输入 01 )</span>
</div>
<div class="output-line" :class="{ active: decResult === 2 }">
<span class="out-val">{{ decResult === 2 ? '1' : '0' }}</span>
<span class="label">Y2 (当输入 10 )</span>
</div>
<div class="output-line" :class="{ active: decResult === 3 }">
<span class="out-val">{{ decResult === 3 ? '1' : '0' }}</span>
<span class="label">Y3 (当输入 11 )</span>
</div>
</div>
</div>
<div class="logic-explain">
<p>
当前输入为二进制的 {{ decA1 ? '1' : '0'
}}{{ decA0 ? '1' : '0' }} (十进制 {{ decResult }})因此只有
<strong>Y{{ decResult }}</strong> 被激活输出 1
</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const tabs = [
{ id: 'mux', name: '多路选择器 (MUX)' },
{ id: 'decoder', name: '译码器 (Decoder)' }
]
const currentTab = ref('mux')
// MUX State
const muxD0 = ref(false)
const muxD1 = ref(true)
const muxSel = ref(false)
const muxResult = computed(() => (muxSel.value ? muxD1.value : muxD0.value))
// Decoder State
const decA1 = ref(false)
const decA0 = ref(false)
const decResult = computed(() => (decA1.value ? 2 : 0) + (decA0.value ? 1 : 0))
</script>
<style scoped>
.functional-unit-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: 0.75rem;
letter-spacing: 0.2px;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 0.5rem;
}
.tab-btn {
padding: 0.4rem 0.8rem;
font-size: 0.85rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
cursor: pointer;
transition: all 0.2s;
}
.tab-btn:hover {
border-color: var(--vp-c-brand-1);
}
.tab-btn.active {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
font-weight: bold;
}
.panel-desc {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 1rem;
}
/* common elements */
.toggle-btn {
width: 2rem;
height: 2rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
font-weight: bold;
font-family: monospace;
cursor: pointer;
transition: all 0.2s;
}
.toggle-btn.on {
background: var(--vp-c-green-soft, #dcfce7);
color: var(--vp-c-green-1, #16a34a);
border-color: var(--vp-c-green-1, #16a34a);
}
.out-val {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
font-weight: bold;
font-family: monospace;
}
.output-line.active .out-val {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
}
.output-line.active .label {
color: var(--vp-c-brand-1);
font-weight: bold;
}
.logic-explain {
margin-top: 1rem;
padding: 0.8rem;
background: var(--vp-c-bg);
border-radius: 6px;
font-size: 0.85rem;
text-align: center;
color: var(--vp-c-text-2);
}
/* MUX Layout */
.mux-container {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 1rem;
}
.inputs {
display: flex;
flex-direction: column;
gap: 1rem;
}
.input-line {
display: flex;
align-items: center;
gap: 0.5rem;
}
.label {
font-size: 0.8rem;
color: var(--vp-c-text-3);
font-variant-numeric: tabular-nums;
}
.mux-chip {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.chip-body {
width: 4rem;
height: 6rem;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
clip-path: polygon(0 0, 100% 20%, 100% 80%, 0 100%);
}
.select-pin {
position: absolute;
bottom: -2.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.3rem;
}
.select-btn {
width: 2rem;
height: 1.5rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
font-size: 0.8rem;
cursor: pointer;
}
.select-btn.on {
background: #fef08a; /* yellow soft */
color: #a16207;
border-color: #a16207;
}
/* Decoder Layout */
.decoder-container {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 1rem;
}
.inputs.vertical,
.outputs.vertical-out {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.decoder-chip .chip-body {
width: 5rem;
height: 8rem;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
text-align: center;
clip-path: none;
border-radius: 4px;
}
</style>
@@ -1,56 +1,56 @@
<template>
<div class="logic-gate-demo">
<div class="demo-header">
<span class="title">逻辑门用开关做运算</span>
<div class="demo-label">四种基本逻辑门 真值表一览</div>
<div class="gates-grid">
<div v-for="gate in gates" :key="gate.name" class="gate-card">
<div class="gate-name">{{ gate.name }}</div>
<div class="gate-rule">{{ gate.rule }}</div>
<table class="mini-truth">
<thead>
<tr>
<th>A</th>
<th v-if="gate.name !== 'NOT'">B</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in gate.rows" :key="i">
<td>{{ row[0] }}</td>
<td v-if="gate.name !== 'NOT'">{{ row[1] }}</td>
<td class="result-cell" :class="{ one: row[row.length - 1] === 1 }">{{ row[row.length - 1] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<p class="intro">
输入 AB 只能是 0 1四种门按不同规则输出一个 0 1下面表格列出所有 4 种输入组合的结果
</p>
<div class="truth-section">
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>AND</th>
<th>OR</th>
<th>NOT(A)</th>
<th>XOR</th>
</tr>
</thead>
<tbody>
<tr v-for="row in truthRows" :key="`${row.a}-${row.b}`">
<td>{{ row.a }}</td>
<td>{{ row.b }}</td>
<td>{{ row.and }}</td>
<td>{{ row.or }}</td>
<td>{{ row.not }}</td>
<td>{{ row.xor }}</td>
</tr>
</tbody>
</table>
<ul class="col-meaning">
<li><strong>AND</strong>两个都是 1 才输出 1像串联都通才通</li>
<li><strong>OR</strong>有一个 1 就输出 1像并联一通就通</li>
<li><strong>NOT(A)</strong> A 取反0110</li>
<li><strong>XOR</strong>两个不同输出 1相同输出 0</li>
</ul>
</div>
<div class="info-box">
<strong>核心思想</strong>逻辑门用晶体管的开关组合实现这四种运算复杂计算都由它们组合而成
</div>
<div class="demo-caption">所有数字计算都由这四种门的组合实现</div>
</div>
</template>
<script setup>
const truthRows = [
{ a: 0, b: 0, and: 0, or: 0, not: 1, xor: 0 },
{ a: 0, b: 1, and: 0, or: 1, not: 1, xor: 1 },
{ a: 1, b: 0, and: 0, or: 1, not: 0, xor: 1 },
{ a: 1, b: 1, and: 1, or: 1, not: 0, xor: 0 }
const gates = [
{
name: 'AND',
rule: '都为 1 才得 1',
rows: [[0,0,0],[0,1,0],[1,0,0],[1,1,1]]
},
{
name: 'OR',
rule: '有一个 1 就得 1',
rows: [[0,0,0],[0,1,1],[1,0,1],[1,1,1]]
},
{
name: 'NOT',
rule: '取反',
rows: [[0,1],[1,0]]
},
{
name: 'XOR',
rule: '不同才得 1',
rows: [[0,0,0],[0,1,1],[1,0,1],[1,1,0]]
}
]
</script>
@@ -59,82 +59,81 @@ const truthRows = [
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-header {
.demo-label {
font-size: 0.78rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.gates-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
}
.gate-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.6rem;
text-align: center;
}
.gate-name {
font-weight: bold;
font-size: 0.9rem;
color: var(--vp-c-brand-1);
margin-bottom: 0.15rem;
}
.gate-rule {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-bottom: 0.5rem;
}
.demo-header .title {
font-weight: bold;
font-size: 1rem;
}
.intro {
font-size: 0.9rem;
color: var(--vp-c-text-2);
margin: 0 0 0.75rem;
line-height: 1.5;
}
.truth-section {
margin-bottom: 0;
}
table {
.mini-truth {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 0.88rem;
margin-bottom: 0.75rem;
font-size: 0.8rem;
font-variant-numeric: tabular-nums;
}
th,
td {
.mini-truth th,
.mini-truth td {
border: 1px solid var(--vp-c-divider);
padding: 0.4rem 0.5rem;
vertical-align: middle;
padding: 0.2rem 0.3rem;
text-align: center;
font-variant-numeric: tabular-nums;
}
th {
.mini-truth th {
background: var(--vp-c-bg-alt);
font-size: 0.72rem;
font-weight: 600;
}
.col-meaning {
margin: 0;
padding-left: 1.25rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
line-height: 1.6;
}
.col-meaning li {
margin-bottom: 0.25rem;
.result-cell.one {
color: var(--vp-c-brand-1);
font-weight: bold;
}
.col-meaning strong {
color: var(--vp-c-text-1);
font-variant-numeric: tabular-nums;
.demo-caption {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-top: 0.6rem;
text-align: center;
}
.info-box {
display: flex;
gap: 0.25rem;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
@media (max-width: 600px) {
.gates-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
@@ -1,80 +1,65 @@
<template>
<div class="memory-demo">
<div class="demo-header">
<span class="title">内存管理程序的"工作台"</span>
<span class="subtitle">操作系统如何分配和管理内存</span>
<div class="demo-controls">
<button class="allocate-btn wechat" @click="allocate('wechat')" :disabled="!hasFreeSpace">
+ 给微信分配数据
</button>
<button class="allocate-btn game" @click="allocate('game')" :disabled="!hasFreeSpace">
+ 给游戏分配数据
</button>
<button class="reset-btn" @click="reset">
重置
</button>
</div>
<div class="demo-content">
<div class="memory-visual">
<div class="mem-header">
<span>虚拟内存空间 (4GB)</span>
<span class="used-info">已用: {{ usedMemory }}MB / 4096MB</span>
</div>
<div class="mem-blocks">
<div
v-for="(block, i) in memoryBlocks"
:key="i"
class="mem-block"
:class="{ allocated: block.allocated, selected: selectedBlock === i }"
:style="{ height: block.size + '%' }"
@click="selectedBlock = i"
>
<span
v-if="block.size > 5"
class="block-label"
>{{ block.name }}</span>
<span
v-if="block.size > 8"
class="block-size"
>{{ block.sizeMB }}MB</span>
</div>
</div>
</div>
<div class="memory-info">
<div class="info-section">
<div class="section-title">
内存分配策略
</div>
<div class="strategy-tabs">
<button
v-for="s in strategies"
:key="s.name"
:class="['strat-btn', { active: activeStrategy === s.name }]"
@click="activeStrategy = s.name"
>
{{ s.name }}
</button>
</div>
<div class="strategy-desc">
{{ currentStrategy.desc }}
<div class="system-view">
<!-- 虚拟内存试图 -->
<div class="virtual-cluster">
<div class="process-vm wechat">
<div class="title">💬 微信的虚拟内存<br/>(它认为自己独占了空间)</div>
<div class="vm-blocks">
<div v-for="i in 4" :key="'w'+i" class="block" :class="{ filled: wechatBlocks >= i }">
{{ wechatBlocks >= i ? '数据 ' + i : '虚拟空闲' }}
</div>
</div>
</div>
<div class="info-section">
<div class="section-title">
虚拟内存的作用
</div>
<div class="vm-benefits">
<div
v-for="b in benefits"
:key="b.title"
class="benefit-item"
>
<span class="benefit-icon">{{ b.icon }}</span>
<div class="benefit-content">
<span class="benefit-title">{{ b.title }}</span>
<span class="benefit-desc">{{ b.desc }}</span>
</div>
<div class="process-vm game">
<div class="title">🎮 游戏的虚拟内存<br/>(它也认为自己独占了空间)</div>
<div class="vm-blocks">
<div v-for="i in 4" :key="'g'+i" class="block" :class="{ filled: gameBlocks >= i }">
{{ gameBlocks >= i ? '数据 ' + i : '虚拟空闲' }}
</div>
</div>
</div>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>虚拟内存让每个进程都以为自己独占整个内存空间实际由操作系统统一管理和映射实现隔离和保护
<!-- OS 页表 (映射表) -->
<div class="os-page-table">
<div class="title">保安大叔 (OS 页表)</div>
<div class="table-info">
当程序存数据时<br/>由我暗中转移到真正的物理缝隙里
</div>
</div>
<!-- 物理内存 -->
<div class="physical-memory">
<div class="title">🗄 真实的物理内存条<br/>(其实像个大杂烩一样乱)</div>
<div class="pm-blocks">
<div
v-for="(block, idx) in physicalBlocks"
:key="'p'+idx"
class="block"
:class="[block.type, { occupied: block.type !== 'empty' }]"
>
{{ block.label }}
</div>
</div>
</div>
</div>
<div class="explanation-box" v-if="wechatBlocks > 0 || gameBlocks > 0">
💡 发现了没尽管右侧真正的物理内存已经被塞得像个狗皮膏药但在左侧的微信和游戏眼里自己的内存条永远是连续且干净的更重要的是微信绝对访问不到橘色的物理块保证了安全
</div>
</div>
</template>
@@ -82,215 +67,273 @@
<script setup>
import { ref, computed } from 'vue'
const selectedBlock = ref(0)
const activeStrategy = ref('首次适应')
const wechatBlocks = ref(0)
const gameBlocks = ref(0)
const memoryBlocks = ref([
{ name: '内核空间', size: 25, allocated: true, sizeMB: 1024 },
{ name: '进程A', size: 15, allocated: true, sizeMB: 600 },
{ name: '空闲', size: 5, allocated: false, sizeMB: 200 },
{ name: '进程B', size: 20, allocated: true, sizeMB: 800 },
{ name: '空闲', size: 10, allocated: false, sizeMB: 400 },
{ name: '进程C', size: 10, allocated: true, sizeMB: 400 },
{ name: '空闲', size: 15, allocated: false, sizeMB: 600 }
])
const strategies = [
{ name: '首次适应', desc: '从内存开始找,找到第一个足够大的空闲块就分配。速度快,但可能产生小碎片。' },
{ name: '最佳适应', desc: '找最小的能满足需求的空闲块。内存利用率高,但可能产生很多小碎片。' },
{ name: '最坏适应', desc: '找最大的空闲块分配。减少小碎片,但大块内存很快用完。' }
// 初始物理内存状态,模拟碎片化
// empty = 空, os = 系统占用
const initialPhysicalBlocks = [
{ type: 'os', label: '系统核心占用' },
{ type: 'empty', label: '空闲' },
{ type: 'os', label: '系统保留' },
{ type: 'empty', label: '空闲' },
{ type: 'empty', label: '空闲' },
{ type: 'empty', label: '空闲' },
{ type: 'os', label: '系统驱动' },
{ type: 'empty', label: '空闲' },
]
const benefits = [
{ icon: '🔒', title: '内存隔离', desc: '进程间互不干扰,一个崩溃不影响其他' },
{ icon: '📦', title: '内存保护', desc: '防止进程访问不该访问的内存区域' },
{ icon: '💾', title: '内存扩展', desc: '用磁盘当内存用,突破物理内存限制' }
]
const physicalBlocks = ref(JSON.parse(JSON.stringify(initialPhysicalBlocks)))
const currentStrategy = computed(() => {
return strategies.find(s => s.name === activeStrategy.value)
const freeSpaceCount = computed(() => {
return physicalBlocks.value.filter(b => b.type === 'empty').length
})
const usedMemory = computed(() => {
return memoryBlocks.value
.filter(b => b.allocated)
.reduce((sum, b) => sum + b.sizeMB, 0)
})
const hasFreeSpace = computed(() => freeSpaceCount.value > 0)
const allocate = (process) => {
if (!hasFreeSpace.value) return
// Find a process block logic
if (process === 'wechat' && wechatBlocks.value < 4) {
wechatBlocks.value++
fillRandomEmptyBlock('wechat', `微信数据 ${wechatBlocks.value}`)
} else if (process === 'game' && gameBlocks.value < 4) {
gameBlocks.value++
fillRandomEmptyBlock('game', `游戏数据 ${gameBlocks.value}`)
}
}
const fillRandomEmptyBlock = (type, label) => {
const emptyIndices = []
physicalBlocks.value.forEach((b, i) => {
if (b.type === 'empty') emptyIndices.push(i)
})
if (emptyIndices.length > 0) {
const randomIndex = emptyIndices[Math.floor(Math.random() * emptyIndices.length)]
physicalBlocks.value[randomIndex] = { type, label }
}
}
const reset = () => {
wechatBlocks.value = 0
gameBlocks.value = 0
physicalBlocks.value = JSON.parse(JSON.stringify(initialPhysicalBlocks))
}
</script>
<style scoped>
.memory-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.demo-content {
.demo-controls {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
justify-content: center;
flex-wrap: wrap;
}
.memory-visual {
flex: 1;
min-width: 200px;
.allocate-btn {
color: white;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.allocate-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
filter: grayscale(1);
}
.allocate-btn.wechat {
background: var(--vp-c-success-1);
}
.allocate-btn.wechat:not(:disabled):hover {
filter: brightness(1.1);
}
.allocate-btn.game {
background: var(--vp-c-warning-1);
}
.allocate-btn.game:not(:disabled):hover {
filter: brightness(1.1);
}
.mem-header {
.reset-btn {
background: transparent;
color: var(--vp-c-text-2);
border: 1px solid var(--vp-c-divider);
padding: 0.6rem 1.2rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.reset-btn:hover {
background: var(--vp-c-bg-mute);
color: var(--vp-c-text-1);
}
.system-view {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
margin-bottom: 0.5rem;
padding: 0.25rem 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
align-items: stretch;
gap: 1.5rem;
}
.used-info {
color: var(--vp-c-brand);
@media (max-width: 768px) {
.system-view {
flex-direction: column;
}
}
.title {
font-size: 0.85rem;
font-weight: bold;
text-align: center;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
min-height: 2.5rem;
}
.mem-blocks {
.virtual-cluster {
display: flex;
gap: 1rem;
flex: 2;
}
.process-vm {
flex: 1;
background: var(--vp-c-bg-alt);
border: 2px dashed var(--vp-c-divider);
border-radius: 10px;
padding: 1rem;
}
.vm-blocks {
display: flex;
flex-direction: column;
gap: 2px;
height: 250px;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
overflow: hidden;
gap: 0.5rem;
}
.mem-block {
.block {
padding: 0.6rem;
border-radius: 6px;
text-align: center;
font-size: 0.8rem;
font-weight: bold;
transition: all 0.3s;
}
.process-vm .block {
background: var(--vp-c-bg-mute);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-3);
opacity: 0.5;
}
.process-vm.wechat .block.filled {
background: rgba(16, 185, 129, 0.15);
border: 1px solid var(--vp-c-success-1);
color: var(--vp-c-success-1);
opacity: 1;
}
.process-vm.game .block.filled {
background: rgba(245, 158, 11, 0.15);
border: 1px solid var(--vp-c-warning-1);
color: var(--vp-c-warning-1);
opacity: 1;
}
.os-page-table {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
background: var(--vp-c-bg-alt);
border-radius: 10px;
padding: 1rem;
position: relative;
border: 2px solid var(--vp-c-brand-1);
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.mem-block.allocated {
background: var(--vp-c-brand-soft);
}
.mem-block:not(.allocated) {
background: var(--vp-c-bg);
border: 1px dashed var(--vp-c-divider);
}
.mem-block.selected {
outline: 2px solid var(--vp-c-brand);
}
.block-label {
font-size: 0.75rem;
font-weight: bold;
}
.block-size {
font-size: 0.65rem;
color: var(--vp-c-text-2);
}
.memory-info {
flex: 1;
min-width: 280px;
}
.info-section {
margin-bottom: 1rem;
}
.section-title {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.strategy-tabs {
display: flex;
gap: 0.25rem;
margin-bottom: 0.5rem;
}
.strat-btn {
padding: 0.25rem 0.5rem;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 4px;
font-size: 0.75rem;
cursor: pointer;
}
.strat-btn.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.strategy-desc {
.os-page-table .table-info {
font-size: 0.8rem;
color: var(--vp-c-text-2);
text-align: center;
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
padding: 0.8rem;
border-radius: 8px;
}
.vm-benefits {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.benefit-item {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg);
border-radius: 4px;
}
.benefit-icon {
font-size: 1.2rem;
}
.benefit-content {
display: flex;
flex-direction: column;
}
.benefit-title {
font-weight: bold;
font-size: 0.85rem;
}
.benefit-desc {
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.info-box {
.physical-memory {
flex: 1;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
border-radius: 10px;
padding: 1rem;
border: 2px solid var(--vp-c-text-3);
}
.pm-blocks {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.pm-blocks .block {
padding: 0.5rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-3);
font-size: 0.75rem;
}
.pm-blocks .block.os {
background: var(--vp-c-bg-mute);
color: var(--vp-c-text-2);
border-style: dashed;
}
.pm-blocks .block.wechat {
background: var(--vp-c-success-1);
color: white;
border-color: var(--vp-c-success-1);
animation: popIn 0.3s ease-out;
}
.pm-blocks .block.game {
background: var(--vp-c-warning-1);
color: white;
border-color: var(--vp-c-warning-1);
animation: popIn 0.3s ease-out;
}
@keyframes popIn {
0% { transform: scale(0.9); opacity: 0; }
50% { transform: scale(1.05); }
100% { transform: scale(1); opacity: 1; }
}
.explanation-box {
margin-top: 1.5rem;
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
@@ -1,272 +1,356 @@
<template>
<div class="process-demo">
<div class="demo-header">
<span class="title">进程程序的"分身术"</span>
<span class="subtitle">一个程序如何同时运行多个实例</span>
<div class="controls-section">
<button class="action-btn" :class="{ active: isRunning }" @click="toggleSimulation">
{{ isRunning ? ' 暂停时间片轮转' : ' 启动 CPU' }}
</button>
<div class="speed-control">
<label>时间流速:</label>
<button :class="{ active: speed === 'slow' }" @click="setSpeed('slow')">极慢动作</button>
<button :class="{ active: speed === 'fast' }" @click="setSpeed('fast')">真实速度</button>
</div>
</div>
<div class="demo-content">
<div class="process-list">
<div class="process-header">
<span class="col-name">进程名</span>
<span class="col-pid">PID</span>
<span class="col-state">状态</span>
<span class="col-mem">内存</span>
<div class="cpu-container">
<div class="cpu-core" :class="{ active: isRunning }">
<div class="cpu-title">单核 CPU</div>
<div class="current-task">
<span v-if="activeProcess" class="task-badge">
正在处理: {{ activeProcess.icon }} {{ activeProcess.name }}
</span>
<span v-else class="task-badge idle">
空闲中...
</span>
</div>
</div>
<!-- 连接线动画 -->
<div class="connector">
<div
v-for="p in processes"
:key="p.pid"
class="process-item"
:class="{ running: p.state === '运行中', selected: selectedPid === p.pid }"
@click="selectedPid = p.pid"
>
<span class="col-name">
<span class="process-icon">{{ p.icon }}</span>
{{ p.name }}
</span>
<span class="col-pid">{{ p.pid }}</span>
<span class="col-state">
<span
class="state-badge"
:class="p.state === '运行中' ? 'running' : 'waiting'"
>
{{ p.state }}
</span>
</span>
<span class="col-mem">{{ p.memory }}</span>
</div>
</div>
<div
v-if="selectedProcess"
class="process-detail"
>
<div class="detail-title">
进程详情{{ selectedProcess.name }}
</div>
<div class="detail-grid">
<div class="detail-item">
<span class="label">进程ID (PID)</span>
<span class="value">{{ selectedProcess.pid }}</span>
</div>
<div class="detail-item">
<span class="label">父进程ID</span>
<span class="value">{{ selectedProcess.ppid }}</span>
</div>
<div class="detail-item">
<span class="label">内存占用</span>
<span class="value">{{ selectedProcess.memory }}</span>
</div>
<div class="detail-item">
<span class="label">CPU 占用</span>
<span class="value">{{ selectedProcess.cpu }}%</span>
</div>
</div>
<div class="memory-layout">
<div class="layout-title">
进程内存布局
</div>
<div class="layout-visual">
<div
v-for="seg in memorySegments"
:key="seg.name"
class="segment"
:style="{ height: seg.height }"
>
<span class="seg-name">{{ seg.name }}</span>
</div>
</div>
class="data-flow"
:class="[ `flow-${activeProcessId}`, { running: isRunning }]">
</div>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>进程是程序的"运行实例"同一个程序可以启动多个进程每个进程有独立的内存空间互不干扰
<div class="processes-grid">
<div
v-for="p in processes"
:key="p.id"
class="process-card"
:class="{ active: p.id === activeProcessId }"
>
<div class="p-header">
<div class="p-title">
<span class="icon">{{ p.icon }}</span>
<span class="name">{{ p.name }}</span>
</div>
<span class="status-badge" :class="p.id === activeProcessId ? 'running' : 'waiting'">
{{ p.id === activeProcessId ? '独占 CPU' : '排队等待' }}
</span>
</div>
<div class="p-progress">
<div class="progress-track">
<div class="progress-fill" :style="{ width: p.progress + '%' }"></div>
</div>
<div class="progress-text">{{ Math.floor(p.progress) }}% 完成</div>
</div>
</div>
</div>
<div class="explanation-box" :class="{ show: isRunning && speed === 'fast' }">
💡 **关键启示**当切换速度足够快时肉眼已经无法分辨谁在等待这也就是为什么只有一个 CPU 核心的电脑依然能让你一边听歌一边打字
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onUnmounted } from 'vue'
const selectedPid = ref(1001)
const isRunning = ref(false)
const activeProcessId = ref(null)
const speed = ref('slow')
let interval = null
const processes = ref([
{ pid: 1001, name: 'Chrome', icon: '🌐', state: '运行中', memory: '512MB', cpu: 15, ppid: 1 },
{ pid: 1002, name: 'VS Code', icon: '📝', state: '运行中', memory: '384MB', cpu: 8, ppid: 1 },
{ pid: 1003, name: '微信', icon: '💬', state: '等待中', memory: '256MB', cpu: 2, ppid: 1 },
{ pid: 1004, name: '终端', icon: '⬛', state: '等待中', memory: '32MB', cpu: 0, ppid: 1002 },
{ pid: 1005, name: '音乐', icon: '🎵', state: '运行中', memory: '128MB', cpu: 3, ppid: 1 }
{ id: 1, name: '微信接收', icon: '💬', progress: 0 },
{ id: 2, name: '音乐播放', icon: '🎵', progress: 0 },
{ id: 3, name: '游戏渲染', icon: '🎮', progress: 0 }
])
const selectedProcess = computed(() => {
return processes.value.find(p => p.pid === selectedPid.value)
})
const activeProcess = computed(() => processes.value.find(p => p.id === activeProcessId.value))
const memorySegments = [
{ name: '栈区 (Stack)', height: '20%' },
{ name: '堆区 (Heap)', height: '35%' },
{ name: '数据段 (Data)', height: '15%' },
{ name: '代码段 (Text)', height: '30%' }
]
const setSpeed = (s) => {
speed.value = s
if (isRunning.value) {
clearInterval(interval)
startLoop()
}
}
const startLoop = () => {
const switchTime = speed.value === 'slow' ? 1200 : 80; // 慢动作 1.2s,快动作极快
if (!activeProcessId.value) {
activeProcessId.value = 1
}
interval = setInterval(() => {
// 增加当前进度
const curr = processes.value.find(p => p.id === activeProcessId.value)
if (curr) {
curr.progress += (speed.value === 'slow' ? 15 : 4)
if (curr.progress >= 100) curr.progress = 0
}
// 切换下一个
let nextId = activeProcessId.value + 1
if (nextId > 3) nextId = 1
activeProcessId.value = nextId
}, switchTime)
}
const toggleSimulation = () => {
if (isRunning.value) {
clearInterval(interval)
isRunning.value = false
activeProcessId.value = null
} else {
isRunning.value = true
startLoop()
}
}
onUnmounted(() => {
if (interval) clearInterval(interval)
})
</script>
<style scoped>
.process-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
.controls-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.action-btn {
background: var(--vp-c-brand-1);
color: white;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 160px;
}
.action-btn.active {
background: var(--vp-c-danger-1);
}
.action-btn:hover {
filter: brightness(1.1);
transform: translateY(-1px);
}
.speed-control {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
font-size: 0.9rem;
color: var(--vp-c-text-2);
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.demo-content {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.process-list {
flex: 1;
min-width: 280px;
font-size: 0.85rem;
}
.process-header {
display: grid;
grid-template-columns: 2fr 1fr 1.5fr 1fr;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
font-weight: bold;
margin-bottom: 0.25rem;
}
.process-item {
display: grid;
grid-template-columns: 2fr 1fr 1.5fr 1fr;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 4px;
.speed-control button {
background: transparent;
border: 1px solid var(--vp-c-divider);
padding: 0.3rem 0.8rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.process-item:hover {
background: var(--vp-c-bg);
}
.process-item.selected {
.speed-control button.active {
background: var(--vp-c-brand-soft);
}
.process-icon {
margin-right: 0.25rem;
}
.state-badge {
padding: 0.1rem 0.4rem;
border-radius: 3px;
font-size: 0.75rem;
}
.state-badge.running {
background: var(--vp-c-success);
color: white;
}
.state-badge.waiting {
background: var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.process-detail {
flex: 1;
min-width: 250px;
}
.detail-title {
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.detail-item {
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
}
.detail-item .label {
display: block;
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.detail-item .value {
font-weight: bold;
font-size: 0.9rem;
}
.memory-layout {
background: var(--vp-c-bg-alt);
padding: 0.5rem;
border-radius: 4px;
}
.layout-title {
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.layout-visual {
.cpu-container {
display: flex;
flex-direction: column;
gap: 2px;
height: 120px;
align-items: center;
margin-bottom: 2rem;
}
.segment {
.cpu-core {
width: 240px;
height: 90px;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: all 0.3s;
position: relative;
}
.cpu-core.active {
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 20px var(--vp-c-brand-soft);
}
.cpu-title {
font-weight: 800;
font-size: 1.1rem;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
letter-spacing: 2px;
}
.current-task {
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: var(--vp-c-brand-soft);
border-radius: 2px;
}
.task-badge {
background: var(--vp-c-brand-1);
color: white;
padding: 0.2rem 0.8rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 600;
}
.task-badge.idle {
background: var(--vp-c-text-3);
}
.seg-name {
font-size: 0.7rem;
/* 连接线动画占位,简化效果,用发亮的虚线替代 */
.connector {
width: 2px;
height: 30px;
background: var(--vp-c-divider);
margin-top: 5px;
position: relative;
}
.processes-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (max-width: 640px) {
.processes-grid {
grid-template-columns: 1fr;
}
}
.process-card {
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 1rem;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.process-card.active {
border-color: var(--vp-c-brand-1);
transform: translateY(-2px);
box-shadow: 0 6px 16px var(--vp-c-brand-soft);
}
.process-card.active::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 4px;
background: var(--vp-c-brand-1);
}
.p-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.p-title {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 600;
}
.status-badge {
font-size: 0.75rem;
padding: 0.1rem 0.5rem;
border-radius: 4px;
font-weight: bold;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
.status-badge.waiting {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.status-badge.running {
background: rgba(16, 185, 129, 0.15);
color: var(--vp-c-success-1);
}
.p-progress {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.progress-track {
width: 100%;
height: 8px;
background: var(--vp-c-bg-soft);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--vp-c-brand-1);
transition: width 0.1s linear;
}
.process-card.active .progress-fill {
background: var(--vp-c-success-1);
}
.progress-text {
font-size: 0.75rem;
color: var(--vp-c-text-2);
text-align: right;
font-variant-numeric: tabular-nums;
}
.explanation-box {
margin-top: 1.5rem;
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
opacity: 0;
transform: translateY(10px);
transition: all 0.5s ease;
}
.explanation-box.show {
opacity: 1;
transform: translateY(0);
}
</style>
@@ -1,60 +1,38 @@
<template>
<div class="register-demo">
<div class="demo-header">
<span class="title">寄存器记住一个 0 1 的小单元</span>
<span class="subtitle">只有点写入时才会把当前输入记下来平时改输入不会影响已存的值</span>
</div>
<div class="demo-label">1 位寄存器 只在"写入"时更新存储值</div>
<div class="why-what-box">
<p class="why-p">
<strong>为啥要看这个</strong>CPU 算到一半要暂时记住中间结果寄存器就是干这个的它和直接连线不同改输入不会立刻改变里面存的东西必须主动点一次写入才会更新
</p>
<p class="what-p">
<strong>这些词是啥</strong>
<span class="term">输入</span>你想写进去的 0 1
<span class="term">写入</span>点一下把当前输入锁进寄存器
<span class="term">存储值</span>寄存器里现在记着的数只有写入时才会变
<span class="term">输出</span>从寄存器读出来的数和存储值一样
</p>
</div>
<div class="control-panel">
<label class="ctrl-group">
<span class="ctrl-label">输入</span>
<div class="reg-panel">
<!-- Input -->
<div class="reg-block">
<span class="reg-title">输入</span>
<button
class="input-toggle"
class="toggle-btn"
:class="{ on: inputData === 1 }"
@click="inputData = inputData === 1 ? 0 : 1"
>
{{ inputData }}
</button>
</label>
<button class="write-btn" :class="{ flash: isWriting }" @click="writeOnce">
写入
</button>
<label class="ctrl-group">
<span class="ctrl-label">存储</span>
<span class="stored-val" :class="{ on: storedData === 1 }">{{ storedData }}</span>
</label>
<span class="ctrl-group">
<span class="ctrl-label">输出</span>
<span class="output-val" :class="{ on: storedData === 1 }">{{ storedData }}</span>
</span>
</div>
<div class="visualization-area">
<div class="flow-strip">
<span class="flow-item">输入 {{ inputData }}</span>
<span class="flow-arrow" :class="{ active: isWriting }">{{ isWriting ? '写入中 →' : '— 点「写入」才更新 →' }}</span>
<span class="flow-item flow-store" :class="{ flash: isWriting }"> {{ storedData }}</span>
>{{ inputData }}</button>
</div>
<!-- Write -->
<button class="write-btn" :class="{ flash: isWriting }" @click="writeOnce">
写入
</button>
<!-- Stored -->
<div class="reg-block">
<span class="reg-title">存储</span>
<span class="val-box" :class="{ on: storedData === 1, flash: isWriting }">{{ storedData }}</span>
</div>
<!-- Output -->
<div class="reg-block">
<span class="reg-title">输出</span>
<span class="val-box out" :class="{ on: storedData === 1 }">{{ storedData }}</span>
</div>
<p class="flow-hint">
{{ inputData !== storedData ? '输入和存储不一样:点「写入」会把当前输入记进去。' : '输入和存储已一致。' }}
</p>
</div>
<div class="info-box">
<strong>核心思想</strong>寄存器只在写入那一刻更新其余时间一直保持原来的值所以 CPU 能稳定保存中间结果
<div class="status-line">
{{ inputData !== storedData ? '⚡ 输入≠存储 → 点"写入"即可更新' : '✓ 输入与存储一致' }}
</div>
</div>
</template>
@@ -69,9 +47,7 @@ const isWriting = ref(false)
const writeOnce = () => {
isWriting.value = true
storedData.value = inputData.value
window.setTimeout(() => {
isWriting.value = false
}, 400)
setTimeout(() => { isWriting.value = false }, 400)
}
</script>
@@ -80,75 +56,44 @@ const writeOnce = () => {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-header {
margin-bottom: 0.5rem;
}
.demo-header .title {
display: block;
.demo-label {
font-size: 0.78rem;
font-weight: bold;
font-size: 1rem;
}
.demo-header .subtitle {
display: block;
font-size: 0.82rem;
color: var(--vp-c-text-2);
font-weight: normal;
}
.why-what-box {
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.65rem 0.85rem;
margin-bottom: 0.75rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
line-height: 1.55;
letter-spacing: 0.2px;
}
.why-what-box .why-p {
margin: 0 0 0.4rem;
}
.why-what-box .what-p {
margin: 0;
}
.why-what-box .term {
font-weight: 600;
color: var(--vp-c-text-1);
}
.control-panel {
/* ── panel ── */
.reg-panel {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.6rem;
padding: 0.5rem 0.7rem;
flex-wrap: wrap;
padding: 0.6rem 0.8rem;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg);
margin-bottom: 0.75rem;
}
.ctrl-group {
display: inline-flex;
.reg-block {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
gap: 0.25rem;
}
.ctrl-label {
font-size: 0.82rem;
color: var(--vp-c-text-2);
.reg-title {
font-size: 0.7rem;
font-weight: 600;
color: var(--vp-c-text-3);
}
.input-toggle {
.toggle-btn {
width: 2.2rem;
height: 2.2rem;
border-radius: 6px;
@@ -156,26 +101,28 @@ const writeOnce = () => {
background: var(--vp-c-bg-alt);
font-weight: bold;
font-size: 1rem;
font-family: 'JetBrains Mono', monospace;
cursor: pointer;
transition: all 0.2s;
}
.input-toggle.on {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
.toggle-btn.on {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
background: var(--vp-c-brand-soft);
}
.write-btn {
padding: 0.35rem 0.8rem;
padding: 0.35rem 0.7rem;
border-radius: 6px;
border: 2px solid var(--vp-c-warning-1, #d97706);
background: var(--vp-c-bg);
color: var(--vp-c-warning-1, #d97706);
font-size: 0.85rem;
font-size: 0.82rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.write-btn:hover {
@@ -187,93 +134,44 @@ const writeOnce = () => {
color: white;
}
.stored-val,
.output-val {
.val-box {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
height: 2rem;
padding: 0 0.4rem;
width: 2.2rem;
height: 2.2rem;
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg-alt);
font-weight: bold;
font-family: monospace;
font-family: 'JetBrains Mono', monospace;
font-size: 1rem;
transition: all 0.2s;
}
.stored-val.on,
.output-val.on {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
.val-box.on {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
background: var(--vp-c-brand-soft);
}
.visualization-area {
margin-bottom: 0.75rem;
.val-box.flash {
box-shadow: 0 0 0 3px var(--vp-c-warning-soft, rgba(217, 119, 6, 0.25));
}
.flow-strip {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.4rem;
padding: 0.6rem 0.8rem;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg);
font-size: 0.9rem;
.val-box.out {
border-style: dashed;
}
.flow-item {
font-weight: bold;
}
.flow-store {
color: var(--vp-c-brand);
}
.flow-store.flash {
box-shadow: 0 0 0 2px var(--vp-c-warning-1);
border-radius: 4px;
}
.flow-arrow {
.status-line {
margin-top: 0.5rem;
font-size: 0.78rem;
color: var(--vp-c-text-3);
font-size: 0.82rem;
}
.flow-arrow.active {
color: var(--vp-c-warning-1);
font-weight: bold;
}
.flow-hint {
margin: 0.4rem 0 0;
font-size: 0.82rem;
color: var(--vp-c-text-2);
}
.info-box {
display: flex;
gap: 0.25rem;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
}
@media (max-width: 520px) {
.control-panel {
flex-direction: column;
align-items: flex-start;
@media (max-width: 480px) {
.reg-panel {
justify-content: center;
}
}
</style>
@@ -0,0 +1,168 @@
<template>
<div class="sand-demo">
<div class="demo-label">从沙子到智能 每一层都是对下一层的封装</div>
<div class="layers">
<div
v-for="(layer, i) in layers" :key="i"
class="layer-row"
:class="{ active: activeLayer === i }"
@mouseenter="activeLayer = i"
@mouseleave="activeLayer = null"
>
<div class="layer-num">{{ i + 1 }}</div>
<div class="layer-icon">{{ layer.icon }}</div>
<div class="layer-body">
<div class="layer-name">{{ layer.name }}</div>
<div class="layer-desc">{{ layer.desc }}</div>
</div>
<div class="layer-scale">{{ layer.scale }}</div>
<div v-if="i < layers.length - 1" class="arrow-down">
<span class="arrow-label">{{ layer.arrow }}</span>
</div>
</div>
</div>
<div class="demo-caption">层层抽象封装最底层的物理材料最终变成通用计算平台</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activeLayer = ref(null)
const layers = [
{ icon: '🏖️', name: '沙子(硅)', desc: '地球上最丰富的元素之一,提炼出高纯度硅', scale: '原材料', arrow: '提纯 → 切割' },
{ icon: '💿', name: '硅晶圆', desc: '直径约 30cm 的单晶硅片,表面极其光滑', scale: '基底', arrow: '光刻 → 蚀刻 → 掺杂' },
{ icon: '🔌', name: '晶体管(开关)', desc: 'Gate=1 导通,Gate=0 断开,用电压控制电流', scale: '数百亿 / 芯片', arrow: '组合成逻辑电路' },
{ icon: '🔲', name: '逻辑门', desc: 'AND / OR / NOT / XOR,实现基本布尔运算', scale: '数十亿', arrow: '组合成功能模块' },
{ icon: '⚙️', name: '功能单元', desc: '加法器、寄存器、多路选择器……各司其职', scale: '数百个', arrow: '集成为处理器' },
{ icon: '🧠', name: 'CPU 核心', desc: 'ALU + 控制器 + 寄存器组,取指→解码→执行→写回', scale: '1128 核', arrow: '软件编程' },
{ icon: '🚀', name: '软件应用', desc: '操作系统 / AI / 游戏 / 网页……一切皆指令', scale: '无限可能', arrow: '' },
]
</script>
<style scoped>
.sand-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: 0.75rem;
letter-spacing: 0.2px;
}
.layers {
display: flex;
flex-direction: column;
gap: 0;
}
.layer-row {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.55rem 0.7rem;
border-radius: 6px;
position: relative;
transition: all 0.15s;
cursor: default;
}
.layer-row:hover,
.layer-row.active {
background: var(--vp-c-bg);
}
.layer-num {
width: 1.4rem;
height: 1.4rem;
border-radius: 50%;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.65rem;
font-weight: bold;
color: var(--vp-c-text-3);
flex-shrink: 0;
}
.layer-row.active .layer-num {
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.layer-icon {
font-size: 1.2rem;
flex-shrink: 0;
}
.layer-body {
flex: 1;
min-width: 0;
}
.layer-name {
font-size: 0.88rem;
font-weight: bold;
color: var(--vp-c-text-1);
line-height: 1.3;
}
.layer-desc {
font-size: 0.75rem;
color: var(--vp-c-text-3);
line-height: 1.4;
}
.layer-scale {
font-size: 0.68rem;
font-weight: 600;
color: var(--vp-c-text-3);
white-space: nowrap;
flex-shrink: 0;
background: var(--vp-c-bg-alt);
padding: 0.15rem 0.45rem;
border-radius: 4px;
}
/* ── arrow between layers ── */
.arrow-down {
position: absolute;
left: 1.1rem;
bottom: -0.55rem;
z-index: 1;
}
.arrow-label {
font-size: 0.6rem;
color: var(--vp-c-text-3);
background: var(--vp-c-bg-soft);
padding: 0 0.3rem;
white-space: nowrap;
}
.demo-caption {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-top: 0.6rem;
text-align: center;
}
@media (max-width: 600px) {
.layer-scale {
display: none;
}
}
</style>
@@ -1,47 +1,45 @@
<template>
<div class="transistor-demo">
<div class="demo-header">
<span class="title">晶体管数字世界的开关</span>
<span class="subtitle">Gate 电压决定电流能否通过</span>
</div>
<div class="demo-label">MOSFET 晶体管示意 点击切换 Gate 电压</div>
<div class="states">
<div class="state-card">
<div class="state-label">Gate = 0低电压</div>
<div class="channel-row">
<span class="terminal">源极</span>
<div class="channel-track off">
<span class="block-icon"> 断开</span>
</div>
<span class="terminal">漏极</span>
</div>
<div class="output-line">输出<strong>0</strong></div>
<div class="schematic" @click="gateOn = !gateOn">
<!-- Source terminal -->
<div class="terminal-box source">
<span class="pin-label">源极<br><span class="en">Source</span></span>
<div class="pin-wire" :class="{ active: gateOn }"></div>
</div>
<div class="state-card">
<div class="state-label">Gate = 1高电压</div>
<div class="channel-row">
<span class="terminal">源极</span>
<div class="channel-track on">
<span class="flow-dot d1" />
<span class="flow-dot d2" />
<span class="flow-dot d3" />
<span class="flow-label">导通</span>
</div>
<span class="terminal">漏极</span>
<!-- Channel -->
<div class="channel-area">
<div class="gate-indicator" :class="{ on: gateOn }">
<span class="gate-label">Gate</span>
<span class="gate-val">{{ gateOn ? '1' : '0' }}</span>
</div>
<div class="output-line">输出<strong>1</strong></div>
<div class="channel-bar" :class="{ conducting: gateOn }">
<template v-if="gateOn">
<span class="electron e1"></span>
<span class="electron e2"></span>
<span class="electron e3"></span>
</template>
<span v-else class="block-mark"></span>
</div>
<div class="channel-status">{{ gateOn ? '导通 → 输出 1' : '断开 → 输出 0' }}</div>
</div>
<!-- Drain terminal -->
<div class="terminal-box drain">
<div class="pin-wire" :class="{ active: gateOn }"></div>
<span class="pin-label">漏极<br><span class="en">Drain</span></span>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>晶体管是电控开关Gate=1 导通Gate=0 断开所有数字计算都建立在这种 0/1 开关之上
</div>
<div class="tap-hint">👆 点击切换 Gate 电压</div>
</div>
</template>
<script setup>
// 纯静态展示,无需交互
import { ref } from 'vue'
const gateOn = ref(false)
</script>
<style scoped>
@@ -49,149 +47,168 @@
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
cursor: pointer;
user-select: none;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title {
.demo-label {
font-size: 0.78rem;
font-weight: bold;
font-size: 1rem;
}
.demo-header .subtitle {
color: var(--vp-c-text-2);
font-size: 0.85rem;
margin-left: 0.5rem;
}
.states {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.state-card {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg);
padding: 0.75rem;
}
.state-label {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
}
.channel-row {
/* ── layout ── */
.schematic {
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
gap: 0;
}
.terminal {
font-size: 0.82rem;
/* ── terminals ── */
.terminal-box {
display: flex;
align-items: center;
gap: 0;
}
.pin-label {
font-size: 0.78rem;
font-weight: 600;
color: var(--vp-c-text-2);
flex-shrink: 0;
line-height: 1.3;
text-align: center;
}
.channel-track {
flex: 1;
min-height: 2.5rem;
.pin-label .en {
font-size: 0.65rem;
color: var(--vp-c-text-3);
font-weight: normal;
}
.pin-wire {
width: 2.5rem;
height: 3px;
background: var(--vp-c-divider);
transition: background 0.3s;
}
.pin-wire.active {
background: var(--vp-c-brand-1);
box-shadow: 0 0 6px var(--vp-c-brand-soft);
}
/* ── channel ── */
.channel-area {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
min-width: 7rem;
}
.gate-indicator {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.25rem 0.65rem;
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
transition: all 0.3s;
}
.gate-indicator.on {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-brand-soft);
}
.gate-label {
font-size: 0.72rem;
font-weight: 600;
color: var(--vp-c-text-2);
}
.gate-val {
font-family: 'JetBrains Mono', monospace;
font-size: 1rem;
font-weight: bold;
color: var(--vp-c-text-1);
transition: color 0.3s;
}
.gate-indicator.on .gate-val {
color: var(--vp-c-brand-1);
}
.channel-bar {
width: 100%;
height: 2rem;
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-alt);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.channel-track.off {
background: var(--vp-c-bg-alt);
.channel-bar.conducting {
background: var(--vp-c-success-soft, rgba(22, 163, 74, 0.12));
border-color: var(--vp-c-success, #16a34a);
}
.channel-track.on {
background: var(--vp-c-success-soft);
border-color: var(--vp-c-success);
}
.block-icon {
.block-mark {
font-size: 0.9rem;
font-weight: bold;
color: var(--vp-c-text-3);
}
.flow-dot {
width: 0.4rem;
height: 0.4rem;
.electron {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--vp-c-success);
background: var(--vp-c-success, #16a34a);
position: absolute;
left: -10%;
animation: flow 1.5s linear infinite;
animation: flow 1.2s linear infinite;
}
.flow-dot.d2 {
animation-delay: 0.45s;
}
.flow-dot.d3 {
animation-delay: 0.9s;
}
.flow-label {
margin-left: 0.4rem;
font-size: 0.85rem;
color: var(--vp-c-success-1);
font-weight: 600;
}
.electron.e2 { animation-delay: 0.4s; }
.electron.e3 { animation-delay: 0.8s; }
@keyframes flow {
from {
left: -10%;
}
to {
left: 105%;
}
0% { left: -8%; opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { left: 108%; opacity: 0; }
}
.output-line {
margin-top: 0.5rem;
font-size: 0.9rem;
color: var(--vp-c-text-1);
}
.output-line strong {
color: var(--vp-c-brand-1);
}
.info-box {
display: flex;
gap: 0.25rem;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
.channel-status {
font-size: 0.8rem;
font-weight: 600;
color: var(--vp-c-text-2);
transition: color 0.3s;
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
.channel-bar.conducting + .channel-status {
color: var(--vp-c-success, #16a34a);
}
@media (max-width: 640px) {
.states {
grid-template-columns: 1fr;
}
.tap-hint {
text-align: center;
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-top: 0.6rem;
}
@media (max-width: 480px) {
.pin-wire { width: 1.5rem; }
.channel-area { min-width: 5rem; }
}
</style>