feat: 更新附录交互组件和文档
This commit is contained in:
@@ -166,9 +166,9 @@
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">本位:</span>
|
||||
<span class="calc-formula">
|
||||
{{ stages[activeBit]?.a }} ⊕ {{ stages[activeBit]?.b }}
|
||||
{{ stages[activeBit]?.a }} XOR {{ stages[activeBit]?.b }}
|
||||
<span v-if="stages[activeBit]?.cin !== null">
|
||||
⊕ {{ stages[activeBit]?.cin }}</span>
|
||||
XOR {{ stages[activeBit]?.cin }}</span>
|
||||
= <strong>{{ stages[activeBit]?.sum }}</strong>
|
||||
</span>
|
||||
<span class="calc-reason">({{ getSumReason(stages[activeBit]) }})</span>
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
:key="'a' + i"
|
||||
class="bit"
|
||||
:class="{ hl: activeBit === 3 - i }"
|
||||
>{{ b }}</span
|
||||
>
|
||||
>{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ clampedA }}</span>
|
||||
</div>
|
||||
@@ -52,8 +51,7 @@
|
||||
:key="'b' + i"
|
||||
class="bit"
|
||||
:class="{ hl: activeBit === 3 - i }"
|
||||
>{{ b }}</span
|
||||
>
|
||||
>{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ clampedB }}</span>
|
||||
</div>
|
||||
@@ -65,8 +63,7 @@
|
||||
:key="'s' + i"
|
||||
class="bit"
|
||||
:class="{ hl: activeBit === 3 - i }"
|
||||
>{{ b }}</span
|
||||
>
|
||||
>{{ b }}</span>
|
||||
</span>
|
||||
<span class="binary-dec">= {{ fourBitResult }}</span>
|
||||
</div>
|
||||
@@ -94,25 +91,14 @@
|
||||
</span>
|
||||
</div>
|
||||
<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
|
||||
>
|
||||
<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="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
|
||||
>
|
||||
<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>
|
||||
|
||||
@@ -132,9 +132,7 @@
|
||||
<div v-if="coinResult.length" class="coin-result">
|
||||
<div class="result-title">找零方案:</div>
|
||||
<div class="coin-list">
|
||||
<span v-for="(c, i) in coinResult" :key="i" class="coin"
|
||||
>{{ c }}元</span
|
||||
>
|
||||
<span v-for="(c, i) in coinResult" :key="i" class="coin">{{ c }}元</span>
|
||||
</div>
|
||||
<div class="result-summary">
|
||||
共 {{ coinResult.length }} 枚硬币
|
||||
@@ -157,8 +155,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong
|
||||
>算法是解决问题的方法。好的算法能让程序效率提升几个数量级。理解算法思维,比记住具体算法更重要。
|
||||
<strong>核心思想:</strong>算法是解决问题的方法。好的算法能让程序效率提升几个数量级。理解算法思维,比记住具体算法更重要。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<div class="addition-rules">
|
||||
<div class="demo-header">
|
||||
<span class="title">从手算加法到逻辑门</span>
|
||||
<span class="subtitle">计算机如何只用 0 和 1 做数学题?看看这个规律</span>
|
||||
</div>
|
||||
|
||||
<!-- 1. 十进制类比 -->
|
||||
<div class="section">
|
||||
<div class="section-title">第一步:回顾十进制的"进位"</div>
|
||||
<div class="decimal-analogy">
|
||||
<div class="math-column">
|
||||
<div class="math-row">
|
||||
<span class="digit carry-mark">1</span> <!-- 进位标记 -->
|
||||
</div>
|
||||
<div class="math-row">
|
||||
<span class="digit"></span>
|
||||
<span class="digit">7</span>
|
||||
</div>
|
||||
<div class="math-row">
|
||||
<span class="op">+</span>
|
||||
<span class="digit">5</span>
|
||||
</div>
|
||||
<div class="math-line"></div>
|
||||
<div class="math-row result-row">
|
||||
<span class="digit c-color">1</span>
|
||||
<span class="digit s-color">2</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="analogy-text">
|
||||
<p>
|
||||
因为 7 + 5 = 12,这个结果超出了个位能装下的最大数字 (9)。
|
||||
我们把 12 拆成"一个完整的 10"和"剩下的 2":
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
留在当前位置的那个 <span class="badge s-badge">2</span> 被<strong>写在个位</strong>上,这叫 <strong class="s-color">本位 (Sum)</strong>。
|
||||
</li>
|
||||
<li>
|
||||
"完整的 10"向十位<strong>进了一个 1</strong>,叫 <strong class="c-color">进位 (Carry)</strong>。
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 二进制四种情况交互 -->
|
||||
<div class="section">
|
||||
<div class="section-title">第二步:二进制加法的 4 种情况(点点看)</div>
|
||||
<div class="binary-demo">
|
||||
<div class="binary-calc">
|
||||
<button class="bit-btn" :class="{ on: inputA }" @click="inputA = !inputA">{{ inputA ? '1' : '0' }}</button>
|
||||
<span class="op">+</span>
|
||||
<button class="bit-btn" :class="{ on: inputB }" @click="inputB = !inputB">{{ inputB ? '1' : '0' }}</button>
|
||||
<span class="op">=</span>
|
||||
<span class="res-box">
|
||||
<span class="res-bit carry-bit" :class="{ lit: carry }">{{ carry ? '1' : '0' }}</span>
|
||||
<span class="res-bit sum-bit" :class="{ lit: sum }">{{ sum ? '1' : '0' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="binary-explain">
|
||||
<p v-if="!inputA && !inputB">
|
||||
0 + 0 = 0。<br>本位写 <strong>0</strong>,不进位。
|
||||
</p>
|
||||
<p v-if="(!inputA && inputB) || (inputA && !inputB)">
|
||||
{{ inputA ? '1' : '0' }} + {{ inputB ? '1' : '0' }} = 1。<br>本位写 <strong>1</strong>,不进位。
|
||||
</p>
|
||||
<p v-if="inputA && inputB">
|
||||
1 + 1 = 10。<br>
|
||||
二进制"满 2 就进 1"。所以本位写 <strong class="s-color">0</strong>,向左进位 <strong class="c-color">1</strong>。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 找出规律并对应到逻辑门 -->
|
||||
<div class="section mb-0">
|
||||
<div class="section-title">第三步:给规律起个名字(电路化)</div>
|
||||
|
||||
<div class="rules-container">
|
||||
<!-- 所有的 4 种情况一览表 -->
|
||||
<div class="rules-table">
|
||||
<div class="rt-head">
|
||||
<span>A</span><span>B</span><span class="c-color">进位</span><span class="s-color">本位</span>
|
||||
</div>
|
||||
<div class="rt-row" :class="{ active: !inputA && !inputB }"><span>0</span><span>0</span><span>0</span><span>0</span></div>
|
||||
<div class="rt-row" :class="{ active: !inputA && inputB }"> <span>0</span><span>1</span><span>0</span><span>1</span></div>
|
||||
<div class="rt-row" :class="{ active: inputA && !inputB }"> <span>1</span><span>0</span><span>0</span><span>1</span></div>
|
||||
<div class="rt-row" :class="{ active: inputA && inputB }"> <span>1</span><span>1</span><span>1</span><span>0</span></div>
|
||||
</div>
|
||||
|
||||
<div class="rules-text">
|
||||
<div class="rule-card sum-rule" :class="{ active: sum }">
|
||||
<div class="rc-title"><span class="badge s-badge">本位</span> 规律:</div>
|
||||
<div class="rc-desc">
|
||||
只有当输入是 (0,1) 或 (1,0) 时,本位才是 1。<br>
|
||||
<strong>总结:</strong>只有两个输入<strong>不同</strong>时才为 1。<br>
|
||||
<div class="rc-gate">这个规律在电路中叫 <strong>XOR (异或门)</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rule-card carry-rule" :class="{ active: carry }">
|
||||
<div class="rc-title"><span class="badge c-badge">进位</span> 规律:</div>
|
||||
<div class="rc-desc">
|
||||
只有当输入是 (1,1) 时,进位才是 1。<br>
|
||||
<strong>总结:</strong>只有两个输入<strong>都是 1</strong> 时才为 1。<br>
|
||||
<div class="rc-gate">这个规律在电路中叫 <strong>AND (与门)</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const inputA = ref(false)
|
||||
const inputB = ref(false)
|
||||
|
||||
const sum = computed(() => inputA.value !== inputB.value)
|
||||
const carry = computed(() => inputA.value && inputB.value)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.addition-rules {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1.2rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.section {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
|
||||
.section-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand-1);
|
||||
margin-bottom: 0.8rem;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px dashed var(--vp-c-divider);
|
||||
}
|
||||
|
||||
/* 颜色常量 */
|
||||
.s-color { color: #16a34a; font-weight: bold; }
|
||||
.c-color { color: #d97706; font-weight: bold; }
|
||||
.badge { padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.75rem; font-family: monospace; }
|
||||
.s-badge { background: #dcfce7; color: #166534; }
|
||||
.c-badge { background: #fef3c7; color: #92400e; }
|
||||
|
||||
/* 1. 十进制类比 */
|
||||
.decimal-analogy {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.math-column {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
font-family: monospace;
|
||||
font-size: 1.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
}
|
||||
.math-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.digit { width: 1.2rem; text-align: center; }
|
||||
.op { font-weight: bold; color: var(--vp-c-text-3); margin-right: 0.2rem; }
|
||||
.math-line {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--vp-c-text-2);
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
.carry-mark {
|
||||
color: #d97706;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
.analogy-text {
|
||||
flex: 1;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.analogy-text ul { padding-left: 1.2rem; margin-top: 0.5rem; }
|
||||
|
||||
/* 2. 二进制四种情况 */
|
||||
.binary-demo {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.binary-calc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.8rem 1.2rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.bit-btn {
|
||||
width: 3rem; height: 3rem; font-size: 1.5rem; font-weight: bold; font-family: monospace;
|
||||
border-radius: 6px; background: var(--vp-c-bg); border: 2px solid var(--vp-c-divider);
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.bit-btn.on { background: #dbeafe; color: #1d4ed8; border-color: #3b82f6; }
|
||||
.res-box { display: flex; gap: 0.2rem; margin-left: 0.5rem; }
|
||||
.res-bit {
|
||||
width: 3rem; height: 3rem; border-radius: 6px; border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg); font-size: 1.5rem; font-weight: bold; font-family: monospace;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: var(--vp-c-text-3); transition: all 0.2s;
|
||||
}
|
||||
.carry-bit.lit { background: #fef3c7; color: #d97706; border-color: #d97706; }
|
||||
.sum-bit.lit { background: #dcfce7; color: #16a34a; border-color: #16a34a; }
|
||||
|
||||
.binary-explain {
|
||||
flex: 1;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid #3b82f6;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
min-width: 200px;
|
||||
}
|
||||
.binary-explain p { margin: 0; }
|
||||
|
||||
/* 3. 找出规律 */
|
||||
.rules-container {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.rules-table {
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
.rt-head, .rt-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 2rem 3rem 3rem;
|
||||
text-align: center;
|
||||
padding: 0.4rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
.rt-row:last-child { border-bottom: none; }
|
||||
.rt-head { font-weight: bold; font-family: system-ui; font-size: 0.75rem; background: var(--vp-c-bg); }
|
||||
.rt-row.active { background: #dbeafe; font-weight: bold; }
|
||||
|
||||
.rules-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
min-width: 250px;
|
||||
}
|
||||
.rule-card {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem;
|
||||
transition: all 0.2s;
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
.sum-rule.active { border-color: #16a34a; background: #f0fdf4; }
|
||||
.carry-rule.active { border-color: #d97706; background: #fffbeb; }
|
||||
|
||||
.rc-title { font-size: 0.8rem; font-weight: bold; margin-bottom: 0.4rem; color: var(--vp-c-text-1); }
|
||||
.rc-desc { font-size: 0.75rem; color: var(--vp-c-text-2); line-height: 1.5; }
|
||||
.rc-gate {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px dashed var(--vp-c-divider);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.decimal-analogy, .binary-demo, .rules-container { flex-direction: column; align-items: stretch; }
|
||||
.math-column, .rules-table { align-self: center; }
|
||||
}
|
||||
</style>
|
||||
@@ -48,8 +48,7 @@
|
||||
v-for="(task, j) in currentStage.tasks"
|
||||
:key="j"
|
||||
class="task-chip"
|
||||
>{{ task }}</span
|
||||
>
|
||||
>{{ task }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-example">
|
||||
@@ -88,9 +87,7 @@
|
||||
<div class="exec-flow">
|
||||
<span v-for="(step, i) in model.steps" :key="i" class="flow-tag">
|
||||
{{ step }}
|
||||
<span v-if="i < model.steps.length - 1" class="flow-arrow"
|
||||
>→</span
|
||||
>
|
||||
<span v-if="i < model.steps.length - 1" class="flow-arrow">→</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="exec-traits">
|
||||
@@ -104,8 +101,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong
|
||||
>编译器像翻译官,把人类能读懂的代码逐步翻译成机器能执行的指令。六个阶段各司其职:识别单词
|
||||
<strong>核心思想:</strong>编译器像翻译官,把人类能读懂的代码逐步翻译成机器能执行的指令。六个阶段各司其职:识别单词
|
||||
→ 理解语法 → 检查语义 → 生成中间码 → 优化 → 生成机器码。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+10
-10
@@ -146,7 +146,7 @@
|
||||
<span class="gate-name">XOR</span>
|
||||
<span class="gate-cn">异或门</span>
|
||||
</div>
|
||||
<div class="gate-formula">A ⊕ B</div>
|
||||
<div class="gate-formula">A XOR B</div>
|
||||
<div class="gate-desc">不同为 1 → 本位</div>
|
||||
</div>
|
||||
<div class="gate-box" :class="{ active: haCarry }">
|
||||
@@ -154,7 +154,7 @@
|
||||
<span class="gate-name">AND</span>
|
||||
<span class="gate-cn">与门</span>
|
||||
</div>
|
||||
<div class="gate-formula">A ∧ B</div>
|
||||
<div class="gate-formula">A AND B</div>
|
||||
<div class="gate-desc">全 1 为 1 → 进位</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,13 +203,13 @@
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">本位:</span>
|
||||
<span class="calc-formula">A ⊕ B = {{ haA ? '1' : '0' }} ⊕ {{ haB ? '1' : '0' }} =
|
||||
<span class="calc-formula">A XOR B = {{ haA ? '1' : '0' }} XOR {{ haB ? '1' : '0' }} =
|
||||
<strong>{{ haSum ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-reason">({{ haA !== haB ? '不同' : '相同' }})</span>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">进位:</span>
|
||||
<span class="calc-formula">A ∧ B = {{ haA ? '1' : '0' }} ∧ {{ haB ? '1' : '0' }} =
|
||||
<span class="calc-formula">A AND B = {{ haA ? '1' : '0' }} AND {{ haB ? '1' : '0' }} =
|
||||
<strong>{{ haCarry ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-reason">({{ haA && haB ? '全为 1' : '不全为 1' }})</span>
|
||||
</div>
|
||||
@@ -384,15 +384,15 @@
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">中间:</span>
|
||||
<span class="calc-formula">xor1 = A ⊕ B = <strong>{{ faXor1 ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-formula">中间值 = A XOR B = <strong>{{ faXor1 ? '1' : '0' }}</strong></span>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">本位:</span>
|
||||
<span class="calc-formula">Sum = xor1 ⊕ Cin = <strong>{{ faSum ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-formula">本位 = 中间值 XOR Cin = <strong>{{ faSum ? '1' : '0' }}</strong></span>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">进位:</span>
|
||||
<span class="calc-formula">Cout = (A∧B) ∨ (xor1∧Cin) =
|
||||
<span class="calc-formula">进位 = (A AND B) OR (中间值 AND Cin) =
|
||||
<strong>{{ faCarryOut ? '1' : '0' }}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -544,21 +544,21 @@ const gates = [
|
||||
name: 'AND',
|
||||
cn: '与门',
|
||||
symbol: '&',
|
||||
formula: 'A ∧ B',
|
||||
formula: 'A AND B',
|
||||
truth: [0, 0, 0, 1]
|
||||
},
|
||||
{
|
||||
name: 'OR',
|
||||
cn: '或门',
|
||||
symbol: '≥1',
|
||||
formula: 'A ∨ B',
|
||||
formula: 'A OR B',
|
||||
truth: [0, 1, 1, 1]
|
||||
},
|
||||
{
|
||||
name: 'XOR',
|
||||
cn: '异或门',
|
||||
symbol: '=1',
|
||||
formula: 'A ⊕ B',
|
||||
formula: 'A XOR B',
|
||||
truth: [0, 1, 1, 0]
|
||||
},
|
||||
{ name: 'NOT', cn: '非门', symbol: '1', formula: '¬A', truth: [1, 0] }
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
|
||||
<div class="lifecycle-flow">
|
||||
<div class="flow-stage" v-for="(stage, index) in stages" :key="stage.id">
|
||||
<div v-for="(stage, index) in stages" :key="stage.id" class="flow-stage">
|
||||
<div class="stage-header" @click="activeStage = index">
|
||||
<span class="stage-number">{{ index + 1 }}</span>
|
||||
<span class="stage-name">{{ stage.name }}</span>
|
||||
|
||||
+1
-3
@@ -86,9 +86,7 @@
|
||||
<div class="arp-arrow">↓ 广播到局域网</div>
|
||||
<div class="arp-answer">
|
||||
<span class="answer-icon">✅</span>
|
||||
<span class="answer-text"
|
||||
>我是!我的 MAC 地址是 00:11:22:33:44:66</span
|
||||
>
|
||||
<span class="answer-text">我是!我的 MAC 地址是 00:11:22:33:44:66</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-7
@@ -40,9 +40,7 @@
|
||||
<div class="linked-container">
|
||||
<div v-for="(item, i) in linkedData" :key="i" class="linked-node">
|
||||
<span class="node-value">{{ item.value }}</span>
|
||||
<span v-if="i < linkedData.length - 1" class="node-arrow"
|
||||
>→</span
|
||||
>
|
||||
<span v-if="i < linkedData.length - 1" class="node-arrow">→</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="operation-hint">
|
||||
@@ -88,8 +86,7 @@
|
||||
v-for="(item, j) in bucket"
|
||||
:key="j"
|
||||
class="bucket-item"
|
||||
>{{ item }}</span
|
||||
>
|
||||
>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,8 +175,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong
|
||||
>数据结构是数据的"容器",不同的容器有不同的特点。选择合适的数据结构,能让程序效率提升几个数量级。
|
||||
<strong>核心思想:</strong>数据结构是数据的"容器",不同的容器有不同的特点。选择合适的数据结构,能让程序效率提升几个数量级。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+1
-3
@@ -24,9 +24,7 @@
|
||||
<!-- 推荐结果 -->
|
||||
<div v-if="activeScenario" class="recommendation">
|
||||
<div class="rec-header">
|
||||
<span class="rec-title"
|
||||
>推荐使用:{{ currentScenario.recommendation }}</span
|
||||
>
|
||||
<span class="rec-title">推荐使用:{{ currentScenario.recommendation }}</span>
|
||||
</div>
|
||||
|
||||
<div class="rec-reason">
|
||||
|
||||
@@ -50,11 +50,9 @@
|
||||
class="char-item"
|
||||
>
|
||||
<span class="char-display">{{ char }}</span>
|
||||
<span class="char-unicode"
|
||||
>U+{{
|
||||
<span class="char-unicode">U+{{
|
||||
char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')
|
||||
}}</span
|
||||
>
|
||||
}}</span>
|
||||
<span class="char-binary">{{
|
||||
char.charCodeAt(0).toString(2).padStart(8, '0')
|
||||
}}</span>
|
||||
|
||||
+1
-1
@@ -90,10 +90,10 @@
|
||||
<div class="packet-header">数据包</div>
|
||||
<div class="packet-body">
|
||||
<div
|
||||
class="packet-layer"
|
||||
v-for="(layer, index) in currentScenario.transmission
|
||||
.layers"
|
||||
:key="index"
|
||||
class="packet-layer"
|
||||
>
|
||||
<span class="layer-name">{{ layer.name }}:</span>
|
||||
<span class="layer-value">{{ layer.value }}</span>
|
||||
|
||||
+334
-275
@@ -1,333 +1,392 @@
|
||||
<template>
|
||||
<div class="filesystem-demo">
|
||||
<div class="demo-wrapper">
|
||||
<!-- 文件树:逻辑视角 -->
|
||||
<div class="logical-view">
|
||||
<div class="view-title">
|
||||
<span>📁 你的视角 (文件系统)</span>
|
||||
<span class="subtitle">漂亮、整洁的目录树</span>
|
||||
</div>
|
||||
|
||||
<div class="file-tree">
|
||||
<div class="tree-node folder expanded">
|
||||
<span class="icon">💾</span> D盘 (根目录)
|
||||
<div class="demo">
|
||||
<div class="title">📁 你看到的文件 vs 硬盘上的碎片</div>
|
||||
|
||||
<div class="scene">
|
||||
<!-- 文件视图 -->
|
||||
<div class="file-view">
|
||||
<div class="view-label">📂 你看到的(文件夹)</div>
|
||||
<div class="folder-tree">
|
||||
<div class="folder">
|
||||
<span class="folder-icon">📁</span>
|
||||
<span>照片</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node folder expanded">
|
||||
<span class="icon">📂</span> 照片
|
||||
<div class="files">
|
||||
<div
|
||||
class="file-item"
|
||||
:class="{ active: currentFile === 'pet' }"
|
||||
>
|
||||
<span class="file-icon">🖼️</span>
|
||||
<span>宠物.jpg</span>
|
||||
<span class="file-size">2.5MB</span>
|
||||
</div>
|
||||
<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
|
||||
class="file-item"
|
||||
:class="{ active: currentFile === 'trip' }"
|
||||
>
|
||||
<span class="file-icon">🖼️</span>
|
||||
<span>旅游.png</span>
|
||||
<span class="file-size">1.8MB</span>
|
||||
</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) }]"
|
||||
<!-- 读取动画 -->
|
||||
<div class="read-animation" v-if="isReading">
|
||||
<div class="read-text">正在读取...</div>
|
||||
<div class="read-blocks">
|
||||
<div
|
||||
v-for="(block, idx) in readingBlocks"
|
||||
:key="idx"
|
||||
class="read-block"
|
||||
:class="{ read: idx <= readProgress }"
|
||||
:style="{ animationDelay: idx * 0.1 + 's' }"
|
||||
>
|
||||
{{ block }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 硬盘视图 -->
|
||||
<div class="disk-view">
|
||||
<div class="view-label">💾 硬盘实际存储(数据块)</div>
|
||||
<div class="disk-grid">
|
||||
<div
|
||||
v-for="n in 12"
|
||||
:key="n"
|
||||
class="disk-block"
|
||||
:class="[
|
||||
getBlockType(n),
|
||||
{
|
||||
active: isReading && currentBlocks.includes(n),
|
||||
reading: isReading && currentBlocks.indexOf(n) === readProgress
|
||||
}
|
||||
]"
|
||||
>
|
||||
<span class="block-num">{{ n }}</span>
|
||||
<span class="block-content">{{ getBlockContent(n) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation-box" v-if="activeFile">
|
||||
<span v-if="activeFile === 'pet'">
|
||||
💡 宠物.jpg 其实被切碎分别放在了第 3、8、14
|
||||
块。文件系统帮你做好了翻译,你只需双击它!
|
||||
</span>
|
||||
<span v-if="activeFile === 'vacation'">
|
||||
💡 旅游.png 放在了第 5、6 块。
|
||||
</span>
|
||||
<span v-if="activeFile === 'doc'">
|
||||
💡 总结.docx 被分散存放在 10、11、18、22
|
||||
块,如果没有文件系统,你得自己背下这些数字才能打开文件。
|
||||
</span>
|
||||
</div>
|
||||
<div class="explanation-box default" v-else>
|
||||
☝️ 试着点击左侧的文件,看看它们在硬盘里到底长什么样。
|
||||
<div class="explain">
|
||||
<strong>💡 原理:</strong>文件系统把文件切成碎片存在硬盘各处(如宠物.jpg存在第3、7、11块),然后用"账本"记录位置。你看到的整齐文件夹只是账本上的记录。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const activeFile = ref(null)
|
||||
const currentFile = ref('')
|
||||
const isReading = ref(false)
|
||||
const readProgress = ref(-1)
|
||||
const currentBlocks = ref([])
|
||||
|
||||
// 映射关系伪造
|
||||
const fileMap = {
|
||||
pet: [3, 8, 14],
|
||||
vacation: [5, 6],
|
||||
doc: [10, 11, 18, 22]
|
||||
// 文件存储位置
|
||||
const fileLocations = {
|
||||
pet: [3, 7, 11], // 宠物.jpg 存在第3、7、11块
|
||||
trip: [5, 6] // 旅游.png 存在第5、6块
|
||||
}
|
||||
|
||||
const selectFile = (file) => {
|
||||
activeFile.value = file
|
||||
// 每块的内容
|
||||
const blockContents = {
|
||||
3: '宠-1',
|
||||
7: '宠-2',
|
||||
11: '宠-3',
|
||||
5: '旅-1',
|
||||
6: '旅-2'
|
||||
}
|
||||
|
||||
const getBlockOwner = (block) => {
|
||||
for (const [key, blocks] of Object.entries(fileMap)) {
|
||||
if (blocks.includes(block)) return `owner-${key}`
|
||||
}
|
||||
let timer = null
|
||||
let phase = 0
|
||||
|
||||
const getBlockType = (n) => {
|
||||
if (fileLocations.pet.includes(n)) return 'pet'
|
||||
if (fileLocations.trip.includes(n)) return 'trip'
|
||||
return 'empty'
|
||||
}
|
||||
|
||||
const isBlockActive = (block) => {
|
||||
if (!activeFile.value) return false
|
||||
return fileMap[activeFile.value].includes(block)
|
||||
const getBlockContent = (n) => {
|
||||
return blockContents[n] || ''
|
||||
}
|
||||
|
||||
const readingBlocks = computed(() => {
|
||||
return currentBlocks.value.map(b => blockContents[b] || '')
|
||||
})
|
||||
|
||||
const runDemo = () => {
|
||||
switch(phase) {
|
||||
case 0: // 开始读取宠物.jpg
|
||||
currentFile.value = 'pet'
|
||||
currentBlocks.value = fileLocations.pet
|
||||
isReading.value = true
|
||||
readProgress.value = -1
|
||||
phase = 1
|
||||
break
|
||||
case 1: // 逐块读取
|
||||
if (readProgress.value < currentBlocks.value.length - 1) {
|
||||
readProgress.value++
|
||||
} else {
|
||||
phase = 2
|
||||
}
|
||||
break
|
||||
case 2: // 读取完成,暂停
|
||||
isReading.value = false
|
||||
phase = 3
|
||||
break
|
||||
case 3: // 开始读取旅游.png
|
||||
currentFile.value = 'trip'
|
||||
currentBlocks.value = fileLocations.trip
|
||||
isReading.value = true
|
||||
readProgress.value = -1
|
||||
phase = 4
|
||||
break
|
||||
case 4: // 逐块读取
|
||||
if (readProgress.value < currentBlocks.value.length - 1) {
|
||||
readProgress.value++
|
||||
} else {
|
||||
phase = 5
|
||||
}
|
||||
break
|
||||
case 5: // 重置
|
||||
isReading.value = false
|
||||
currentFile.value = ''
|
||||
currentBlocks.value = []
|
||||
phase = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(runDemo, 800)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filesystem-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
.demo {
|
||||
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-wrapper {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
.translator {
|
||||
transform: rotate(90deg);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logical-view,
|
||||
.physical-view {
|
||||
flex: 1;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.view-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px dashed var(--vp-c-divider);
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.tree-node {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
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;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Disk Grid */
|
||||
.disk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.4rem;
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.disk-block {
|
||||
aspect-ratio: 1;
|
||||
|
||||
.scene {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.file-view, .disk-view {
|
||||
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);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.disk-block.owner-pet {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
.view-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.disk-block.owner-vacation {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
|
||||
.folder-tree {
|
||||
padding-left: 8px;
|
||||
}
|
||||
.disk-block.owner-doc {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
|
||||
.folder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.files {
|
||||
padding-left: 20px;
|
||||
border-left: 1px dashed var(--vp-c-divider);
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.file-item.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
margin-left: auto;
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.file-item.active .file-size {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.read-animation {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-brand);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.read-text {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-brand);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.read-blocks {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.read-block {
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 9px;
|
||||
color: var(--vp-c-text-3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.read-block.read {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
animation: pulse 0.3s ease;
|
||||
}
|
||||
|
||||
.disk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.disk-block {
|
||||
aspect-ratio: 1;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 9px;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.disk-block.empty {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.disk-block.pet {
|
||||
background: #16a34a22;
|
||||
border-color: #16a34a55;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.disk-block.trip {
|
||||
background: #3b82f622;
|
||||
border-color: #3b82f655;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.disk-block.active {
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.disk-block.reading {
|
||||
transform: scale(1.1);
|
||||
font-weight: 600;
|
||||
animation: glow 0.5s ease infinite alternate;
|
||||
}
|
||||
|
||||
.disk-block.pet.reading {
|
||||
background: #16a34a;
|
||||
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;
|
||||
.disk-block.trip.reading {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
.explanation-box.default {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-left-color: var(--vp-c-text-3);
|
||||
|
||||
.block-num {
|
||||
font-size: 8px;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.block-content {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.explain {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
padding: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
.explain strong { color: var(--vp-c-text-1); }
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from { box-shadow: 0 0 5px currentColor; }
|
||||
to { box-shadow: 0 0 15px currentColor; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
<template>
|
||||
<div class="flip-flop-wrapper">
|
||||
<div class="header">
|
||||
<div class="title">从触发器到寄存器:记忆的闭环机制</div>
|
||||
<div class="desc">试着改变数据并观察,没有时钟信号的允许,输出重新反馈回输入端的"闭环"长久保护了记忆。</div>
|
||||
</div>
|
||||
|
||||
<div class="interactive-panel">
|
||||
<!-- Left side: Controllable Data inputs -->
|
||||
<div class="data-input-sec">
|
||||
<div class="sec-label">数据总线 (Data Input)</div>
|
||||
<div class="bus-lines">
|
||||
<div
|
||||
v-for="(bit, idx) in inputBits" :key="'in'+idx"
|
||||
class="input-node"
|
||||
:class="{ active: bit === 1 }"
|
||||
@click="toggleInput(idx)"
|
||||
>
|
||||
{{ bit }}
|
||||
<span v-if="bit === 1" class="pulse-ring"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrow indicating flow, blocked by a 'gate' if no clock -->
|
||||
<div class="gate-sec">
|
||||
<div class="sec-label transparent">大门</div>
|
||||
<div class="gate-door-container">
|
||||
<div class="flow-paths">
|
||||
<div v-for="(bit, idx) in inputBits" :key="'path'+idx" class="flow-line" :class="{ active: bit === 1, open: isClockPulsing }">
|
||||
<span v-if="bit === 1 && isClockPulsing" class="data-particle"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gate-door" :class="{ open: isClockPulsing }">
|
||||
<span v-if="!isClockPulsing" class="lock-icon">🔒</span>
|
||||
<span v-else class="lock-icon">🔓</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side: The flip-flops (registers) -->
|
||||
<div class="register-sec" :class="{ writing: isClockPulsing }">
|
||||
<div class="sec-label">4位寄存器 (存储状态)</div>
|
||||
<div class="stored-bits">
|
||||
<div
|
||||
v-for="(bit, idx) in storedBits" :key="'s'+idx"
|
||||
class="store-node-wrapper"
|
||||
>
|
||||
<div class="store-node" :class="{ active: bit === 1 }">
|
||||
{{ bit }}
|
||||
</div>
|
||||
<!-- Individual loop for each bit to vividly show Feedback -->
|
||||
<svg class="node-loop" viewBox="0 0 50 50" aria-hidden="true">
|
||||
<path d="M 40 25 C 50 25 50 45 25 45 C 0 45 0 25 10 25" fill="none" class="loop-stroke" :class="{ active: bit === 1 }" stroke-width="2.5" />
|
||||
<polygon points="6,20 6,30 14,25" class="loop-arrow" :class="{ active: bit === 1 }" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Clock button at bottom -->
|
||||
<div class="clock-sec">
|
||||
<div class="sec-label">控制中心</div>
|
||||
<button class="clock-btn" :class="{ active: isClockPulsing }" @click="triggerClock">
|
||||
<span class="icon">⚡</span> 发送时钟脉冲 (Clock)
|
||||
</button>
|
||||
<div class="status-msg">
|
||||
<strong :class="{ 'warning-text': pendingChanges, 'success-text': !pendingChanges && !isClockPulsing, 'action-text': isClockPulsing }">
|
||||
{{ statusMessage }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const inputBits = ref([1, 0, 1, 0])
|
||||
const storedBits = ref([0, 0, 0, 0])
|
||||
const isClockPulsing = ref(false)
|
||||
const manualStatus = ref('')
|
||||
|
||||
const pendingChanges = computed(() => {
|
||||
return inputBits.value.join('') !== storedBits.value.join('')
|
||||
})
|
||||
|
||||
const statusMessage = computed(() => {
|
||||
if (isClockPulsing.value) {
|
||||
return '脉冲到达!突破闭环防线,正并行读入新数据...'
|
||||
}
|
||||
if (manualStatus.value) return manualStatus.value;
|
||||
return '尝试改变左侧输入,闭环保护期间寄存器值无法更改。'
|
||||
})
|
||||
|
||||
const toggleInput = (idx) => {
|
||||
inputBits.value[idx] = inputBits.value[idx] === 1 ? 0 : 1
|
||||
if (pendingChanges.value) {
|
||||
manualStatus.value = '准备写入新数据,请点击"发送时钟脉冲"打破锁死。'
|
||||
} else {
|
||||
manualStatus.value = '输入总线与当前存储状态相同。'
|
||||
}
|
||||
}
|
||||
|
||||
const triggerClock = () => {
|
||||
if (isClockPulsing.value) return
|
||||
isClockPulsing.value = true
|
||||
manualStatus.value = ''
|
||||
|
||||
// lock in the data exactly halfway through animation
|
||||
setTimeout(() => {
|
||||
storedBits.value = [...inputBits.value]
|
||||
}, 150)
|
||||
|
||||
setTimeout(() => {
|
||||
isClockPulsing.value = false
|
||||
if (pendingChanges.value) {
|
||||
manualStatus.value = '闭环重新生效,但还有未写入的新数据?'
|
||||
} else {
|
||||
manualStatus.value = '脉冲消退。反馈闭环恢复,当前状态被长久稳固保存。'
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flip-flop-wrapper {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header .title {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.header .desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.4rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.sec-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
.transparent {
|
||||
opacity: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.interactive-panel {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-evenly;
|
||||
gap: 1.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--vp-c-divider-light);
|
||||
}
|
||||
|
||||
.data-input-sec, .register-sec {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bus-lines, .stored-bits {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.input-node, .store-node {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
.input-node {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
.input-node:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
.input-node.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.pulse-ring {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px var(--vp-c-brand-1);
|
||||
animation: static-pulse 2s infinite;
|
||||
z-index: -1;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@keyframes static-pulse {
|
||||
0% { transform: scale(1); opacity: 0.6; }
|
||||
50% { transform: scale(1.1); opacity: 0; }
|
||||
100% { transform: scale(1); opacity: 0; }
|
||||
}
|
||||
|
||||
/* Stored */
|
||||
.store-node-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.store-node {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px dashed var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
|
||||
}
|
||||
.store-node.active {
|
||||
border-style: solid;
|
||||
background: rgba(16, 185, 129, 0.08);
|
||||
border-color: #10b981;
|
||||
color: #10b981;
|
||||
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
/* Loop Animation */
|
||||
.node-loop {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
z-index: 1;
|
||||
}
|
||||
.loop-stroke {
|
||||
stroke: var(--vp-c-divider);
|
||||
stroke-dasharray: 4;
|
||||
animation: loop-march 2s linear infinite;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.loop-stroke.active {
|
||||
stroke: #10b981;
|
||||
}
|
||||
.loop-arrow {
|
||||
fill: var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.loop-arrow.active {
|
||||
fill: #10b981;
|
||||
}
|
||||
@keyframes loop-march {
|
||||
from { stroke-dashoffset: 8; }
|
||||
to { stroke-dashoffset: 0; }
|
||||
}
|
||||
|
||||
.register-sec.writing .store-node {
|
||||
transform: scale(1.1);
|
||||
border-color: #eab308;
|
||||
box-shadow: 0 0 20px rgba(234, 179, 8, 0.4);
|
||||
}
|
||||
|
||||
|
||||
/* Gate Section */
|
||||
.gate-sec {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 60px;
|
||||
}
|
||||
.gate-door-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.flow-paths {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
padding-top: 1.4rem;
|
||||
height: 100%;
|
||||
}
|
||||
.flow-line {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
border-bottom: 2px solid var(--vp-c-divider);
|
||||
opacity: 0.2;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.flow-line.active {
|
||||
border-bottom-color: var(--vp-c-brand-1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.flow-line.open.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.data-particle {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--vp-c-brand-1);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 8px var(--vp-c-brand-1);
|
||||
animation: slide-across 0.3s linear forwards;
|
||||
}
|
||||
@keyframes slide-across {
|
||||
0% { left: 0; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
.gate-door {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
width: 8px;
|
||||
background: var(--vp-c-danger-1);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 10px rgba(220, 38, 38, 0.3);
|
||||
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
||||
}
|
||||
.gate-door.open {
|
||||
transform: translateY(-80px);
|
||||
opacity: 0;
|
||||
background: #eab308;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
position: absolute;
|
||||
left: -9px;
|
||||
font-size: 1.2rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
|
||||
/* Clock Section */
|
||||
.clock-sec {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: rgba(234, 179, 8, 0.05);
|
||||
padding: 1.2rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(234, 179, 8, 0.2);
|
||||
}
|
||||
|
||||
.clock-btn {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid #eab308;
|
||||
color: #c2410c;
|
||||
padding: 0.6rem 2rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 4px 10px rgba(234, 179, 8, 0.15);
|
||||
}
|
||||
.dark .clock-btn {
|
||||
color: #fde047;
|
||||
}
|
||||
.clock-btn:hover {
|
||||
background: #eab308;
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 15px rgba(234, 179, 8, 0.3);
|
||||
}
|
||||
.clock-btn.active {
|
||||
background: #eab308;
|
||||
color: white;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.status-msg {
|
||||
margin-top: 1rem;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
}
|
||||
.warning-text { color: var(--vp-c-warning-1); }
|
||||
.success-text { color: var(--vp-c-success-1); transition: color 0.3s; }
|
||||
.action-text { color: #eab308; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.interactive-panel {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.gate-sec {
|
||||
height: 40px;
|
||||
width: 60%;
|
||||
}
|
||||
.gate-door {
|
||||
top: 50%; bottom: auto;
|
||||
width: 100%; height: 8px;
|
||||
left: 0; right: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.gate-door.open {
|
||||
transform: translateY(-50%) translateX(-100px);
|
||||
}
|
||||
.flow-paths {
|
||||
flex-direction: row; height: 100%; width: 100%;
|
||||
align-items: center; justify-content: space-evenly;
|
||||
padding-top: 0;
|
||||
}
|
||||
.flow-line {
|
||||
width: 0; height: 100%;
|
||||
border-bottom: none; border-right: 2px solid var(--vp-c-divider);
|
||||
}
|
||||
.flow-line.active { border-right-color: var(--vp-c-brand-1); }
|
||||
.data-particle {
|
||||
top: 0; left: -4px;
|
||||
animation: slide-down 0.3s linear forwards;
|
||||
}
|
||||
@keyframes slide-down {
|
||||
0% { top: 0; left: -4px; }
|
||||
100% { top: 100%; left: -4px; }
|
||||
}
|
||||
.bus-lines, .stored-bits {
|
||||
flex-direction: row; justify-content: center; flex-wrap: wrap; gap: 0.8rem;
|
||||
}
|
||||
.node-loop { display: none; /* Hide loops on mobile to avoid layout breaking */ }
|
||||
}
|
||||
</style>
|
||||
+239
-454
@@ -1,212 +1,131 @@
|
||||
<template>
|
||||
<div class="full-adder-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">全加器 (Full Adder)</span>
|
||||
<span class="subtitle">能处理进位输入的完整加法单元 ── 三个输入,两个输出</span>
|
||||
<span class="title">全加器 (Full Adder) — 交互演示</span>
|
||||
<span class="subtitle">比半加器多一个输入:来自低位的进位 (Cin)。点击三个输入试试</span>
|
||||
</div>
|
||||
|
||||
<div class="terms-box">
|
||||
<div class="term-item">
|
||||
<span class="term-name">Cin (进位输入)</span>
|
||||
<span class="term-desc">来自低位的进位信号</span>
|
||||
</div>
|
||||
<div class="term-item">
|
||||
<span class="term-name">Sum (本位)</span>
|
||||
<span class="term-desc">三位异或的结果</span>
|
||||
</div>
|
||||
<div class="term-item">
|
||||
<span class="term-name">Cout (进位输出)</span>
|
||||
<span class="term-desc">向高位的进位信号</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 主交互区 -->
|
||||
<div class="main-area">
|
||||
<!-- 左:大号加法展示 -->
|
||||
<div class="left-panel">
|
||||
<div class="big-calc">
|
||||
<button class="big-bit" :class="{ on: inputA }" @click="inputA = !inputA">{{ inputA ? '1' : '0' }}</button>
|
||||
<span class="op">+</span>
|
||||
<button class="big-bit" :class="{ on: inputB }" @click="inputB = !inputB">{{ inputB ? '1' : '0' }}</button>
|
||||
<span class="op">+</span>
|
||||
<button class="big-bit cin" :class="{ on: carryIn }" @click="carryIn = !carryIn">{{ carryIn ? '1' : '0' }}</button>
|
||||
<span class="op">=</span>
|
||||
<span class="result-display">
|
||||
<span class="result-bit carry-bit" :class="{ lit: carryOut }">{{ carryOut ? '1' : '0' }}</span>
|
||||
<span class="result-bit sum-bit" :class="{ lit: sumOut }">{{ sumOut ? '1' : '0' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="circuit-container">
|
||||
<div class="inputs">
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: inputA }"
|
||||
@click="inputA = !inputA"
|
||||
<div class="input-labels">
|
||||
<span class="il">A</span>
|
||||
<span class="il spacer"></span>
|
||||
<span class="il">B</span>
|
||||
<span class="il spacer"></span>
|
||||
<span class="il cin-label">低位进位</span>
|
||||
<span class="il spacer"></span>
|
||||
<span class="result-labels">
|
||||
<span class="rl" :class="{ lit: carryOut }">进位</span>
|
||||
<span class="rl" :class="{ lit: sumOut }">本位</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="explain-box">
|
||||
<div class="explain-text">{{ explainText }}</div>
|
||||
</div>
|
||||
|
||||
<div class="vs-half">
|
||||
<strong>和半加器相比:</strong>全加器多了第三个输入「低位进位 (Cin)」。在多位加法中,每一列不仅要加 A 和 B,还要加上右边那一列传来的进位。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右:真值表 -->
|
||||
<div class="right-panel">
|
||||
<div class="table-title">所有 8 种情况(3个输入 → 2³ = 8)</div>
|
||||
<div class="truth-table">
|
||||
<div class="tr header">
|
||||
<span>A</span><span>B</span><span>Cin</span><span class="sum-col">本位</span><span class="carry-col">进位</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="row in cases"
|
||||
:key="row.key"
|
||||
class="tr"
|
||||
:class="{ active: row.a === +inputA && row.b === +inputB && row.cin === +carryIn }"
|
||||
>
|
||||
{{ inputA ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">输入 A</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: inputB }"
|
||||
@click="inputB = !inputB"
|
||||
>
|
||||
{{ inputB ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">输入 B</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn cin-btn"
|
||||
:class="{ on: carryIn }"
|
||||
@click="carryIn = !carryIn"
|
||||
>
|
||||
{{ carryIn ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">Cin</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wires">
|
||||
<svg class="wire-svg" viewBox="0 0 120 180" preserveAspectRatio="none">
|
||||
<path
|
||||
d="M 0,30 C 30,30 30,45 60,45"
|
||||
fill="none"
|
||||
:stroke="inputA ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 0,30 L 15,30 L 15,105 L 60,105"
|
||||
fill="none"
|
||||
:stroke="inputA ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 0,90 C 30,90 30,60 60,60"
|
||||
fill="none"
|
||||
:stroke="inputB ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 0,90 L 25,90 L 25,120 L 60,120"
|
||||
fill="none"
|
||||
:stroke="inputB ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 0,150 C 30,150 30,135 60,135"
|
||||
fill="none"
|
||||
:stroke="carryIn ? '#d97706' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<circle
|
||||
cx="15"
|
||||
cy="30"
|
||||
r="3"
|
||||
:fill="inputA ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
/>
|
||||
<circle
|
||||
cx="25"
|
||||
cy="90"
|
||||
r="3"
|
||||
:fill="inputB ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="gates">
|
||||
<div class="gate-box xor-gate" :class="{ active: xor1 }">
|
||||
<div class="gate-header">
|
||||
<span class="gate-name">XOR</span>
|
||||
<span class="gate-cn">异或门</span>
|
||||
<span>{{ row.a }}</span>
|
||||
<span>{{ row.b }}</span>
|
||||
<span>{{ row.cin }}</span>
|
||||
<span class="sum-col">{{ row.sum }}</span>
|
||||
<span class="carry-col">{{ row.carry }}</span>
|
||||
</div>
|
||||
<div class="gate-formula">A ⊕ B</div>
|
||||
<div class="gate-desc">不同为 1 → 中间值</div>
|
||||
</div>
|
||||
<div class="gate-box and-gate" :class="{ active: carry1 }">
|
||||
<div class="gate-header">
|
||||
<span class="gate-name">AND</span>
|
||||
<span class="gate-cn">与门</span>
|
||||
</div>
|
||||
<div class="gate-formula">A ∧ B</div>
|
||||
<div class="gate-desc">全 1 为 1 → 进位1</div>
|
||||
</div>
|
||||
<div class="gate-box xor-gate" :class="{ active: sumOut }">
|
||||
<div class="gate-header">
|
||||
<span class="gate-name">XOR</span>
|
||||
<span class="gate-cn">异或门</span>
|
||||
</div>
|
||||
<div class="gate-formula">xor1 ⊕ Cin</div>
|
||||
<div class="gate-desc">不同为 1 → 本位</div>
|
||||
</div>
|
||||
<div class="gate-box or-gate" :class="{ active: carryOut }">
|
||||
<div class="gate-header">
|
||||
<span class="gate-name">OR</span>
|
||||
<span class="gate-cn">或门</span>
|
||||
</div>
|
||||
<div class="gate-formula">c1 ∨ c2</div>
|
||||
<div class="gate-desc">有 1 为 1 → 进位输出</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wires outputs-wires">
|
||||
<svg class="wire-svg" viewBox="0 0 50 180" preserveAspectRatio="none">
|
||||
<line
|
||||
x1="0"
|
||||
y1="52"
|
||||
x2="50"
|
||||
y2="52"
|
||||
:stroke="
|
||||
sumOut ? 'var(--vp-c-green-1, #16a34a)' : 'var(--vp-c-text-3)'
|
||||
"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<line
|
||||
x1="0"
|
||||
y1="135"
|
||||
x2="50"
|
||||
y2="135"
|
||||
:stroke="carryOut ? '#d97706' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="outputs">
|
||||
<div class="output-line" :class="{ active: sumOut }">
|
||||
<span class="label">本位 (Sum)</span>
|
||||
<span class="out-val s-val">{{ sumOut ? '1' : '0' }}</span>
|
||||
</div>
|
||||
<div class="output-line" :class="{ active: carryOut }">
|
||||
<span class="label">Cout (进位)</span>
|
||||
<span class="out-val c-val">{{ carryOut ? '1' : '0' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-box">
|
||||
<div class="calc-title">计算过程</div>
|
||||
<div class="calc-content">
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">输入:</span>
|
||||
<span class="calc-value">A = {{ inputA ? '1' : '0' }},B = {{ inputB ? '1' : '0' }},Cin =
|
||||
{{ carryIn ? '1' : '0' }}</span>
|
||||
<!-- 内部结构:用两个半加器来理解 -->
|
||||
<div class="structure-section">
|
||||
<div class="structure-label">全加器的内部 = 两个半加器串联</div>
|
||||
<div class="structure-row">
|
||||
<!-- 半加器 1 -->
|
||||
<div class="ha-block">
|
||||
<div class="ha-title">第一步:半加器 ①</div>
|
||||
<div class="ha-desc">先算 A + B</div>
|
||||
<div class="ha-io">
|
||||
<div class="ha-in">
|
||||
<span class="io-tag a">A = {{ inputA ? '1' : '0' }}</span>
|
||||
<span class="io-tag b">B = {{ inputB ? '1' : '0' }}</span>
|
||||
</div>
|
||||
<div class="ha-arrow">→</div>
|
||||
<div class="ha-out">
|
||||
<span class="io-result" :class="{ lit: xor1 }">中间和: {{ xor1 ? '1' : '0' }}</span>
|
||||
<span class="io-result carry" :class="{ lit: carry1 }">进位①: {{ carry1 ? '1' : '0' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">中间值:</span>
|
||||
<span class="calc-formula">xor1 = A ⊕ B = {{ inputA ? '1' : '0' }} ⊕
|
||||
{{ inputB ? '1' : '0' }} =
|
||||
<strong>{{ xor1 ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-reason">({{ inputA !== inputB ? '不同' : '相同' }})</span>
|
||||
|
||||
<div class="chain-arrow">▸</div>
|
||||
|
||||
<!-- 半加器 2 -->
|
||||
<div class="ha-block">
|
||||
<div class="ha-title">第二步:半加器 ②</div>
|
||||
<div class="ha-desc">把中间和 + 低位进位</div>
|
||||
<div class="ha-io">
|
||||
<div class="ha-in">
|
||||
<span class="io-tag mid">中间和 = {{ xor1 ? '1' : '0' }}</span>
|
||||
<span class="io-tag cin">Cin = {{ carryIn ? '1' : '0' }}</span>
|
||||
</div>
|
||||
<div class="ha-arrow">→</div>
|
||||
<div class="ha-out">
|
||||
<span class="io-result sum" :class="{ lit: sumOut }">本位: {{ sumOut ? '1' : '0' }}</span>
|
||||
<span class="io-result carry" :class="{ lit: carry2 }">进位②: {{ carry2 ? '1' : '0' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">本位:</span>
|
||||
<span class="calc-formula">Sum = xor1 ⊕ Cin = {{ xor1 ? '1' : '0' }} ⊕
|
||||
{{ carryIn ? '1' : '0' }} =
|
||||
<strong>{{ sumOut ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-reason">({{ xor1 !== carryIn ? '不同' : '相同' }})</span>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">进位:</span>
|
||||
<span class="calc-formula">Cout = (A∧B) ∨ (xor1∧Cin) = ({{ carry1 ? '1' : '0' }}) ∨ ({{
|
||||
carry2 ? '1' : '0'
|
||||
}}) = <strong>{{ carryOut ? '1' : '0' }}</strong></span>
|
||||
|
||||
<div class="chain-arrow">▸</div>
|
||||
|
||||
<!-- OR 合并 -->
|
||||
<div class="or-block">
|
||||
<div class="ha-title">第三步:合并进位</div>
|
||||
<div class="ha-desc">两路进位只要有一个是 1,就向高位进 1</div>
|
||||
<div class="ha-io">
|
||||
<div class="ha-in">
|
||||
<span class="io-tag c1">进位① = {{ carry1 ? '1' : '0' }}</span>
|
||||
<span class="io-tag c2">进位② = {{ carry2 ? '1' : '0' }}</span>
|
||||
</div>
|
||||
<div class="ha-arrow">→</div>
|
||||
<div class="ha-out">
|
||||
<span class="io-result cout" :class="{ lit: carryOut }">最终进位: {{ carryOut ? '1' : '0' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>
|
||||
全加器 = 两个半加器 + 一个 OR 门。第一级半加器算
|
||||
A+B,第二级半加器把结果加上 Cin,OR 门合并两路进位信号。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -214,302 +133,168 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const inputA = ref(true)
|
||||
const inputB = ref(true)
|
||||
const inputB = ref(false)
|
||||
const carryIn = ref(false)
|
||||
|
||||
// 第一步:半加器 1
|
||||
const xor1 = computed(() => inputA.value !== inputB.value)
|
||||
const carry1 = computed(() => inputA.value && inputB.value)
|
||||
const carry2 = computed(() => xor1.value && carryIn.value)
|
||||
|
||||
// 第二步:半加器 2
|
||||
const sumOut = computed(() => xor1.value !== carryIn.value)
|
||||
const carry2 = computed(() => xor1.value && carryIn.value)
|
||||
|
||||
// 第三步:OR 合并
|
||||
const carryOut = computed(() => carry1.value || carry2.value)
|
||||
|
||||
const cases = [
|
||||
{ a: 0, b: 0, cin: 0, sum: 0, carry: 0, key: '000' },
|
||||
{ a: 0, b: 0, cin: 1, sum: 1, carry: 0, key: '001' },
|
||||
{ a: 0, b: 1, cin: 0, sum: 1, carry: 0, key: '010' },
|
||||
{ a: 0, b: 1, cin: 1, sum: 0, carry: 1, key: '011' },
|
||||
{ a: 1, b: 0, cin: 0, sum: 1, carry: 0, key: '100' },
|
||||
{ a: 1, b: 0, cin: 1, sum: 0, carry: 1, key: '101' },
|
||||
{ a: 1, b: 1, cin: 0, sum: 0, carry: 1, key: '110' },
|
||||
{ a: 1, b: 1, cin: 1, sum: 1, carry: 1, key: '111' },
|
||||
]
|
||||
|
||||
const explainText = computed(() => {
|
||||
const a = +inputA.value
|
||||
const b = +inputB.value
|
||||
const c = +carryIn.value
|
||||
const total = a + b + c
|
||||
if (total === 0) return '0 + 0 + 0 = 0。本位写 0,不进位。'
|
||||
if (total === 1) return `${a} + ${b} + ${c} = 1。本位写 1,不进位。`
|
||||
if (total === 2) return `${a} + ${b} + ${c} = 2。二进制里 2 就是 "10",所以本位写 0,向左进 1。`
|
||||
return `${a} + ${b} + ${c} = 3。二进制里 3 就是 "11",所以本位写 1,向左进 1。`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.full-adder-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem 1.2rem;
|
||||
padding: 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.demo-header { margin-bottom: 1rem; }
|
||||
.title { display: block; font-size: 0.95rem; font-weight: bold; color: var(--vp-c-text-1); }
|
||||
.subtitle { font-size: 0.75rem; color: var(--vp-c-text-3); }
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
margin-bottom: 0.75rem;
|
||||
/* main area */
|
||||
.main-area { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
|
||||
.left-panel { flex: 1; min-width: 220px; }
|
||||
.right-panel { flex: 1; min-width: 220px; }
|
||||
|
||||
/* big calc */
|
||||
.big-calc { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.2rem; flex-wrap: wrap; }
|
||||
.big-bit {
|
||||
width: 2.8rem; height: 2.8rem; font-size: 1.3rem; font-weight: bold; font-family: monospace;
|
||||
border-radius: 6px; background: var(--vp-c-bg); border: 2px solid var(--vp-c-divider);
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.big-bit.on { background: #dbeafe; color: #1d4ed8; border-color: #3b82f6; }
|
||||
.big-bit.cin.on { background: #fef3c7; color: #d97706; border-color: #d97706; }
|
||||
.op { font-size: 1.2rem; color: var(--vp-c-text-3); font-weight: bold; }
|
||||
|
||||
.title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
.result-display { display: flex; gap: 0.15rem; }
|
||||
.result-bit {
|
||||
width: 2.8rem; height: 2.8rem; border-radius: 6px; border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg); font-size: 1.3rem; font-weight: bold; font-family: monospace;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: var(--vp-c-text-3); transition: all 0.2s;
|
||||
}
|
||||
.carry-bit.lit { background: #fef3c7; color: #d97706; border-color: #d97706; }
|
||||
.sum-bit.lit { background: #dcfce7; color: #16a34a; border-color: #16a34a; }
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
.input-labels { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.6rem; flex-wrap: wrap; }
|
||||
.il { font-size: 0.65rem; color: var(--vp-c-text-3); text-align: center; width: 2.8rem; }
|
||||
.il.spacer { width: 1rem; }
|
||||
.cin-label { color: #d97706; font-weight: 600; }
|
||||
.result-labels { display: flex; gap: 0.15rem; }
|
||||
.rl { font-size: 0.6rem; color: var(--vp-c-text-3); text-align: center; width: 2.8rem; transition: all 0.2s; }
|
||||
.rl.lit:first-child { color: #d97706; font-weight: bold; }
|
||||
.rl.lit:last-child { color: #16a34a; font-weight: bold; }
|
||||
|
||||
.explain-box {
|
||||
background: var(--vp-c-bg); border-radius: 6px; padding: 0.6rem 0.8rem;
|
||||
border-left: 3px solid var(--vp-c-brand-1); margin-bottom: 0.6rem;
|
||||
}
|
||||
.explain-text { font-size: 0.82rem; color: var(--vp-c-text-2); line-height: 1.5; }
|
||||
|
||||
.terms-box {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 6px;
|
||||
.vs-half {
|
||||
font-size: 0.78rem; color: var(--vp-c-text-2); line-height: 1.4;
|
||||
padding: 0.5rem 0.7rem; background: var(--vp-c-bg-alt); border-radius: 6px;
|
||||
}
|
||||
.vs-half strong { color: var(--vp-c-text-1); }
|
||||
|
||||
.term-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
/* truth table */
|
||||
.table-title { font-size: 0.75rem; font-weight: 600; color: var(--vp-c-text-2); margin-bottom: 0.4rem; }
|
||||
.truth-table { border-radius: 6px; overflow: hidden; border: 1px solid var(--vp-c-divider); }
|
||||
.tr {
|
||||
display: grid; grid-template-columns: 1fr 1fr 1fr 1.5fr 1.5fr;
|
||||
padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--vp-c-divider);
|
||||
font-family: monospace; font-size: 0.78rem; transition: all 0.2s;
|
||||
}
|
||||
|
||||
.term-name {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand-1);
|
||||
.tr:last-child { border-bottom: none; }
|
||||
.tr.header {
|
||||
background: var(--vp-c-bg-alt); font-weight: bold; font-family: system-ui;
|
||||
font-size: 0.7rem; color: var(--vp-c-text-2);
|
||||
}
|
||||
.tr.active { background: var(--vp-c-brand-soft); font-weight: bold; }
|
||||
.sum-col { color: #16a34a; }
|
||||
.carry-col { color: #d97706; }
|
||||
|
||||
.term-desc {
|
||||
font-size: 0.68rem;
|
||||
color: var(--vp-c-text-3);
|
||||
line-height: 1.3;
|
||||
/* structure section */
|
||||
.structure-section { border-top: 1px solid var(--vp-c-divider); padding-top: 1rem; }
|
||||
.structure-label { font-size: 0.8rem; font-weight: 600; color: var(--vp-c-text-1); margin-bottom: 0.6rem; }
|
||||
|
||||
.structure-row { display: flex; align-items: stretch; gap: 0.3rem; overflow-x: auto; }
|
||||
|
||||
.ha-block, .or-block {
|
||||
flex: 1; min-width: 160px; padding: 0.6rem; border-radius: 8px;
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
.or-block { border-color: #d97706; background: #fffbeb; }
|
||||
|
||||
.circuit-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
.ha-title { font-size: 0.72rem; font-weight: bold; color: var(--vp-c-text-1); margin-bottom: 0.15rem; }
|
||||
.ha-desc { font-size: 0.65rem; color: var(--vp-c-text-3); margin-bottom: 0.4rem; }
|
||||
|
||||
.ha-io { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }
|
||||
.ha-in { display: flex; flex-direction: column; gap: 0.2rem; }
|
||||
.ha-arrow { font-size: 0.8rem; color: var(--vp-c-text-3); padding: 0 0.15rem; }
|
||||
.ha-out { display: flex; flex-direction: column; gap: 0.2rem; }
|
||||
|
||||
.io-tag {
|
||||
font-size: 0.65rem; font-family: monospace; padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px; background: var(--vp-c-bg-alt); color: var(--vp-c-text-2);
|
||||
}
|
||||
.io-tag.a { border-left: 2px solid #3b82f6; }
|
||||
.io-tag.b { border-left: 2px solid #8b5cf6; }
|
||||
.io-tag.cin { border-left: 2px solid #d97706; }
|
||||
.io-tag.mid { border-left: 2px solid #16a34a; }
|
||||
.io-tag.c1 { border-left: 2px solid #d97706; }
|
||||
.io-tag.c2 { border-left: 2px solid #d97706; }
|
||||
|
||||
.inputs,
|
||||
.outputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
min-width: 6rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.outputs {
|
||||
min-width: 8rem;
|
||||
}
|
||||
|
||||
.input-line,
|
||||
.output-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
.io-result {
|
||||
font-size: 0.68rem; font-family: monospace; padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px; background: var(--vp-c-bg-alt); color: var(--vp-c-text-3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.io-result.lit { background: #dcfce7; color: #16a34a; font-weight: bold; }
|
||||
.io-result.carry.lit { background: #fef3c7; color: #d97706; }
|
||||
.io-result.sum.lit { background: #dcfce7; color: #16a34a; }
|
||||
.io-result.cout.lit { background: #fef3c7; color: #d97706; }
|
||||
|
||||
.toggle-btn.on {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
.chain-arrow {
|
||||
display: flex; align-items: center; font-size: 1.2rem; color: var(--vp-c-text-3);
|
||||
flex-shrink: 0; padding: 0 0.1rem;
|
||||
}
|
||||
|
||||
.toggle-btn.cin-btn.on {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
.out-val {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.output-line.active .s-val {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
border-color: #16a34a;
|
||||
}
|
||||
|
||||
.output-line.active .c-val {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
.wires {
|
||||
width: 100px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outputs-wires {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.wire-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gates {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.6rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.gate-box {
|
||||
width: 6rem;
|
||||
height: 3.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.gate-box.active {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
box-shadow: 0 0 8px var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.gate-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.gate-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.gate-cn {
|
||||
font-size: 0.65rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.gate-formula {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.gate-desc {
|
||||
font-size: 0.6rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
.calculation-box {
|
||||
margin-top: 1rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.calc-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.calc-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.calc-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.calc-label {
|
||||
color: var(--vp-c-text-3);
|
||||
min-width: 4rem;
|
||||
}
|
||||
|
||||
.calc-formula {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.calc-formula strong {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.calc-reason {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.circuit-container {
|
||||
transform: scale(0.75);
|
||||
transform-origin: left top;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.terms-box {
|
||||
flex-direction: column;
|
||||
}
|
||||
.gates {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.main-area { flex-direction: column; }
|
||||
.structure-row { flex-direction: column; }
|
||||
.chain-arrow { transform: rotate(90deg); justify-content: center; padding: 0.3rem 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
+2
-4
@@ -20,8 +20,7 @@
|
||||
<!-- MUX Demo -->
|
||||
<div v-if="currentTab === 'mux'" class="demo-panel">
|
||||
<div class="panel-desc">
|
||||
<strong>多路选择器 (MUX)</strong
|
||||
>:像铁路道岔一样,根据"选择信号"决定让哪一路数据通过。
|
||||
<strong>多路选择器 (MUX)</strong>:像铁路道岔一样,根据"选择信号"决定让哪一路数据通过。
|
||||
</div>
|
||||
<div class="mux-container">
|
||||
<div class="inputs">
|
||||
@@ -81,8 +80,7 @@
|
||||
<!-- Decoder Demo -->
|
||||
<div v-if="currentTab === 'decoder'" class="demo-panel">
|
||||
<div class="panel-desc">
|
||||
<strong>译码器 (Decoder)</strong
|
||||
>:将二进制输入转换为特定输出线的激活信号(例如 2位输入可以激活
|
||||
<strong>译码器 (Decoder)</strong>:将二进制输入转换为特定输出线的激活信号(例如 2位输入可以激活
|
||||
4根输出线中的一根)。
|
||||
</div>
|
||||
<div class="decoder-container">
|
||||
|
||||
+1
-1
@@ -37,9 +37,9 @@
|
||||
</div>
|
||||
<div class="change-process">
|
||||
<div
|
||||
class="process-step"
|
||||
v-for="(step, index) in changeSteps"
|
||||
:key="index"
|
||||
class="process-step"
|
||||
>
|
||||
<div class="step-coin">{{ step.coin }}</div>
|
||||
<div class="step-text">× {{ step.count }} = {{ step.value }}元</div>
|
||||
|
||||
+287
-349
@@ -1,167 +1,116 @@
|
||||
<template>
|
||||
<div class="half-adder-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">半加器 (Half Adder)</span>
|
||||
<span class="subtitle">最基础的二进制加法单元 ── 只能处理两个 1 位输入</span>
|
||||
<span class="title">半加器 (Half Adder) — 交互演示</span>
|
||||
<span class="subtitle">点击输入 A / B,看看这一位加法的结果</span>
|
||||
</div>
|
||||
|
||||
<div class="terms-box">
|
||||
<div class="term-item">
|
||||
<span class="term-name">本位 (Sum)</span>
|
||||
<span class="term-desc">当前位的计算结果,不考虑外部进位</span>
|
||||
</div>
|
||||
<div class="term-item">
|
||||
<span class="term-name">进位 (Carry)</span>
|
||||
<span class="term-desc">当两位都是 1 时,向更高位"借位"</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="circuit-container">
|
||||
<div class="inputs">
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: inputA }"
|
||||
@click="inputA = !inputA"
|
||||
>
|
||||
<!-- 主交互区 -->
|
||||
<div class="main-area">
|
||||
<!-- 左:输入和直观结果 -->
|
||||
<div class="left-panel">
|
||||
<div class="big-calc">
|
||||
<button class="big-bit" :class="{ on: inputA }" @click="inputA = !inputA">
|
||||
{{ inputA ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">输入 A</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ on: inputB }"
|
||||
@click="inputB = !inputB"
|
||||
>
|
||||
<span class="op">+</span>
|
||||
<button class="big-bit" :class="{ on: inputB }" @click="inputB = !inputB">
|
||||
{{ inputB ? '1' : '0' }}
|
||||
</button>
|
||||
<span class="label">输入 B</span>
|
||||
<span class="op">=</span>
|
||||
<span class="result-display">
|
||||
<span class="result-bit carry-bit" :class="{ lit: carryOut }">{{ carryOut ? '1' : '0' }}</span>
|
||||
<span class="result-bit sum-bit" :class="{ lit: sumOut }">{{ sumOut ? '1' : '0' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="result-labels">
|
||||
<span class="rl carry-label" :class="{ lit: carryOut }">▲ 进位:向左边那列借一个 1</span>
|
||||
<span class="rl sum-label" :class="{ lit: sumOut }">▲ 本位:这一列写下的数字</span>
|
||||
</div>
|
||||
|
||||
<div class="explain-box">
|
||||
<div class="explain-text">{{ explainText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wires">
|
||||
<svg class="wire-svg" viewBox="0 0 100 150" preserveAspectRatio="none">
|
||||
<path
|
||||
d="M 0,30 C 50,30 50,40 100,40"
|
||||
fill="none"
|
||||
:stroke="inputA ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 0,120 C 50,120 50,60 100,60"
|
||||
fill="none"
|
||||
:stroke="inputB ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 20,30 L 20,90 C 20,90 50,90 100,90"
|
||||
fill="none"
|
||||
:stroke="inputA ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 40,120 L 40,110 C 40,110 50,110 100,110"
|
||||
fill="none"
|
||||
:stroke="inputB ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<circle
|
||||
cx="20"
|
||||
cy="30"
|
||||
r="3"
|
||||
:fill="inputA ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
/>
|
||||
<circle
|
||||
cx="40"
|
||||
cy="120"
|
||||
r="3"
|
||||
:fill="inputB ? 'var(--vp-c-brand-1)' : 'var(--vp-c-text-3)'"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="gates">
|
||||
<div class="gate-box xor-gate" :class="{ active: sumOut }">
|
||||
<div class="gate-header">
|
||||
<span class="gate-name">XOR</span>
|
||||
<span class="gate-cn">异或门</span>
|
||||
<!-- 右:四种情况对照表,高亮当前行 -->
|
||||
<div class="right-panel">
|
||||
<div class="table-title">所有可能的情况</div>
|
||||
<div class="truth-table">
|
||||
<div class="tr header">
|
||||
<span>A</span><span>B</span><span class="sum-col">写下(本位)</span><span class="carry-col">进位</span>
|
||||
</div>
|
||||
<div class="gate-formula">A ⊕ B</div>
|
||||
<div class="gate-desc">不同为 1 → 本位</div>
|
||||
</div>
|
||||
<div class="gate-box and-gate" :class="{ active: carryOut }">
|
||||
<div class="gate-header">
|
||||
<span class="gate-name">AND</span>
|
||||
<span class="gate-cn">与门</span>
|
||||
<div
|
||||
v-for="row in cases"
|
||||
:key="row.a + '' + row.b"
|
||||
class="tr"
|
||||
:class="{ active: row.a === +inputA && row.b === +inputB }"
|
||||
>
|
||||
<span>{{ row.a }}</span>
|
||||
<span>{{ row.b }}</span>
|
||||
<span class="sum-col">{{ row.sum }}</span>
|
||||
<span class="carry-col">{{ row.carry }}</span>
|
||||
</div>
|
||||
<div class="gate-formula">A ∧ B</div>
|
||||
<div class="gate-desc">全 1 为 1 → 进位</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wires outputs-wires">
|
||||
<svg class="wire-svg" viewBox="0 0 50 150" preserveAspectRatio="none">
|
||||
<line
|
||||
x1="0"
|
||||
y1="50"
|
||||
x2="50"
|
||||
y2="50"
|
||||
:stroke="
|
||||
sumOut ? 'var(--vp-c-green-1, #16a34a)' : 'var(--vp-c-text-3)'
|
||||
"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<line
|
||||
x1="0"
|
||||
y1="100"
|
||||
x2="50"
|
||||
y2="100"
|
||||
:stroke="carryOut ? '#d97706' : 'var(--vp-c-text-3)'"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="outputs">
|
||||
<div class="output-line" :class="{ active: sumOut }">
|
||||
<span class="label">本位 (Sum)</span>
|
||||
<span class="out-val s-val">{{ sumOut ? '1' : '0' }}</span>
|
||||
</div>
|
||||
<div class="output-line" :class="{ active: carryOut }">
|
||||
<span class="label">进位 (Carry)</span>
|
||||
<span class="out-val c-val">{{ carryOut ? '1' : '0' }}</span>
|
||||
<div class="pattern-note">
|
||||
<p>仔细看这张表,你会发现两个规律:</p>
|
||||
<ul>
|
||||
<li>「写下」列:只有 A 和 B <strong>不一样</strong>时才是 1 → 这个规律叫 <code>XOR(异或)</code></li>
|
||||
<li>「进位」列:只有 A 和 B <strong>都是 1</strong> 时才是 1 → 这个规律叫 <code>AND(与)</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-box">
|
||||
<div class="calc-title">计算过程</div>
|
||||
<div class="calc-content">
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">输入:</span>
|
||||
<span class="calc-value">A = {{ inputA ? '1' : '0' }},B = {{ inputB ? '1' : '0' }}</span>
|
||||
<!-- 电路连接图 -->
|
||||
<div class="circuit-section">
|
||||
<div class="circuit-label">电路是这样连的:</div>
|
||||
<div class="circuit-row">
|
||||
<div class="wire-inputs">
|
||||
<div class="wire-bit a-bit" :class="{ on: inputA }">A = {{ inputA ? '1' : '0' }}</div>
|
||||
<div class="wire-bit b-bit" :class="{ on: inputB }">B = {{ inputB ? '1' : '0' }}</div>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">本位:</span>
|
||||
<span class="calc-formula">A ⊕ B = {{ inputA ? '1' : '0' }} ⊕ {{ inputB ? '1' : '0' }} =
|
||||
<strong>{{ sumOut ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-reason">({{ inputA !== inputB ? '不同' : '相同' }})</span>
|
||||
<svg class="split-svg" viewBox="0 0 60 80" preserveAspectRatio="none">
|
||||
<!-- A 到 XOR -->
|
||||
<line x1="0" y1="20" x2="30" y2="20" :stroke="inputA ? '#3b82f6' : '#ccc'" stroke-width="2" />
|
||||
<line x1="30" y1="20" x2="60" y2="15" :stroke="inputA ? '#3b82f6' : '#ccc'" stroke-width="2" />
|
||||
<!-- A 到 AND -->
|
||||
<line x1="30" y1="20" x2="60" y2="65" :stroke="inputA ? '#3b82f6' : '#ccc'" stroke-width="2" />
|
||||
<!-- 分支点 -->
|
||||
<circle cx="30" cy="20" r="3" :fill="inputA ? '#3b82f6' : '#ccc'" />
|
||||
<!-- B 到 XOR -->
|
||||
<line x1="0" y1="60" x2="30" y2="60" :stroke="inputB ? '#8b5cf6' : '#ccc'" stroke-width="2" />
|
||||
<line x1="30" y1="60" x2="60" y2="25" :stroke="inputB ? '#8b5cf6' : '#ccc'" stroke-width="2" />
|
||||
<!-- B 到 AND -->
|
||||
<line x1="30" y1="60" x2="60" y2="75" :stroke="inputB ? '#8b5cf6' : '#ccc'" stroke-width="2" />
|
||||
<circle cx="30" cy="60" r="3" :fill="inputB ? '#8b5cf6' : '#ccc'" />
|
||||
</svg>
|
||||
<div class="gates-col">
|
||||
<div class="gate-chip xor" :class="{ active: sumOut }">
|
||||
<div class="chip-name">XOR 异或门</div>
|
||||
<div class="chip-rule">不同 → 1</div>
|
||||
<div class="chip-out">输出: <strong>{{ sumOut ? '1' : '0' }}</strong></div>
|
||||
</div>
|
||||
<div class="gate-chip and" :class="{ active: carryOut }">
|
||||
<div class="chip-name">AND 与门</div>
|
||||
<div class="chip-rule">全1 → 1</div>
|
||||
<div class="chip-out">输出: <strong>{{ carryOut ? '1' : '0' }}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calc-row">
|
||||
<span class="calc-label">进位:</span>
|
||||
<span class="calc-formula">A ∧ B = {{ inputA ? '1' : '0' }} ∧ {{ inputB ? '1' : '0' }} =
|
||||
<strong>{{ carryOut ? '1' : '0' }}</strong></span>
|
||||
<span class="calc-reason">({{ inputA && inputB ? '全为 1' : '不全为 1' }})</span>
|
||||
<svg class="out-svg" viewBox="0 0 40 80" preserveAspectRatio="none">
|
||||
<line x1="0" y1="20" x2="40" y2="20" :stroke="sumOut ? '#16a34a' : '#ccc'" stroke-width="2" />
|
||||
<line x1="0" y1="60" x2="40" y2="60" :stroke="carryOut ? '#d97706' : '#ccc'" stroke-width="2" />
|
||||
</svg>
|
||||
<div class="output-col">
|
||||
<div class="out-chip sum" :class="{ active: sumOut }">
|
||||
本位 (Sum)<br><strong>{{ sumOut ? '1' : '0' }}</strong>
|
||||
</div>
|
||||
<div class="out-chip carry" :class="{ active: carryOut }">
|
||||
进位 (Carry)<br><strong>{{ carryOut ? '1' : '0' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>
|
||||
半加器用 XOR 算"本位和",用 AND
|
||||
算"进位"。它是最小的加法单元,但无法处理来自低位的进位。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -169,288 +118,277 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const inputA = ref(false)
|
||||
const inputB = ref(true)
|
||||
const inputB = ref(false)
|
||||
|
||||
const sumOut = computed(() => inputA.value !== inputB.value)
|
||||
const carryOut = computed(() => inputA.value && inputB.value)
|
||||
|
||||
const cases = [
|
||||
{ a: 0, b: 0, sum: 0, carry: 0 },
|
||||
{ a: 0, b: 1, sum: 1, carry: 0 },
|
||||
{ a: 1, b: 0, sum: 1, carry: 0 },
|
||||
{ a: 1, b: 1, sum: 0, carry: 1 },
|
||||
]
|
||||
|
||||
const explainText = computed(() => {
|
||||
const a = +inputA.value
|
||||
const b = +inputB.value
|
||||
if (a === 0 && b === 0) return '0 + 0 = 0。这一列写下 0,不需要进位。'
|
||||
if (a === 0 && b === 1) return '0 + 1 = 1。这一列写下 1,不需要进位。'
|
||||
if (a === 1 && b === 0) return '1 + 0 = 1。这一列写下 1,不需要进位。'
|
||||
return '1 + 1 = 2。但二进制这一列最多写 1,所以写下 0,并且向左边那列"进一个 1"(进位)。就像十进制的 9+1=10,个位写 0、十位进 1。'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.half-adder-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem 1.2rem;
|
||||
padding: 1.2rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
margin-bottom: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 0.9rem;
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.terms-box {
|
||||
/* ── main area ── */
|
||||
.main-area {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 6px;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.term-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
/* left */
|
||||
.left-panel { flex: 1; min-width: 200px; }
|
||||
|
||||
.term-name {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.term-desc {
|
||||
font-size: 0.68rem;
|
||||
color: var(--vp-c-text-3);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.circuit-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.inputs,
|
||||
.outputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3.5rem;
|
||||
min-width: 6rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.outputs {
|
||||
min-width: 8rem;
|
||||
}
|
||||
|
||||
.input-line,
|
||||
.output-line {
|
||||
.big-calc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
.big-bit {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.toggle-btn.on {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
.big-bit.on {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.out-val {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 4px;
|
||||
.op {
|
||||
font-size: 1.5rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-display {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
.result-bit {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 6px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.output-line.active .s-val {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
border-color: #16a34a;
|
||||
}
|
||||
.output-line.active .c-val {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
.wires {
|
||||
width: 80px;
|
||||
height: 150px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outputs-wires {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.wire-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
z-index: 2;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.gate-box {
|
||||
width: 7.5rem;
|
||||
height: 4rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.gate-box.active {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
box-shadow: 0 0 8px var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.gate-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.gate-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.gate-cn {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-text-3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.carry-bit.lit { background: #fef3c7; color: #d97706; border-color: #d97706; }
|
||||
.sum-bit.lit { background: #dcfce7; color: #16a34a; border-color: #16a34a; }
|
||||
|
||||
.gate-formula {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
.result-labels {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
margin-top: 0.2rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.gate-desc {
|
||||
.rl {
|
||||
font-size: 0.65rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-top: 0.15rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.carry-label.lit { color: #d97706; font-weight: bold; }
|
||||
.sum-label.lit { color: #16a34a; font-weight: bold; }
|
||||
|
||||
.calculation-box {
|
||||
margin-top: 1rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
.explain-box {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-left: 3px solid var(--vp-c-brand-1);
|
||||
}
|
||||
.explain-text {
|
||||
font-size: 0.82rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.calc-title {
|
||||
/* right */
|
||||
.right-panel { flex: 1; min-width: 200px; }
|
||||
|
||||
.table-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.calc-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
.truth-table { border-radius: 6px; overflow: hidden; border: 1px solid var(--vp-c-divider); margin-bottom: 0.75rem; }
|
||||
.tr {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 2fr 1.5fr;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
font-family: monospace;
|
||||
font-size: 0.82rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tr:last-child { border-bottom: none; }
|
||||
.tr.header {
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-weight: bold;
|
||||
font-family: system-ui;
|
||||
font-size: 0.72rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
.tr.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
font-weight: bold;
|
||||
}
|
||||
.sum-col { color: #16a34a; }
|
||||
.carry-col { color: #d97706; }
|
||||
.tr.active .sum-col { color: #16a34a; }
|
||||
.tr.active .carry-col { color: #d97706; }
|
||||
|
||||
.calc-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.3rem;
|
||||
.pattern-note {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 0.6rem 0.8rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.calc-label {
|
||||
color: var(--vp-c-text-3);
|
||||
min-width: 3.5rem;
|
||||
}
|
||||
|
||||
.calc-formula {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.calc-formula strong {
|
||||
.pattern-note p { margin: 0 0 0.4rem 0; }
|
||||
.pattern-note ul { margin: 0; padding-left: 1.2rem; }
|
||||
.pattern-note li { margin-bottom: 0.3rem; line-height: 1.4; }
|
||||
.pattern-note code {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.05rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.calc-reason {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 0.72rem;
|
||||
/* ── circuit section ── */
|
||||
.circuit-section {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
.circuit-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.circuit-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
.wire-inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.wire-bit {
|
||||
padding: 0.3rem 0.6rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
transition: all 0.2s;
|
||||
min-width: 4.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.wire-bit.on { background: #dbeafe; color: #1d4ed8; border-color: #3b82f6; }
|
||||
|
||||
.split-svg { width: 50px; height: 80px; flex-shrink: 0; }
|
||||
|
||||
.gates-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.gate-chip {
|
||||
width: 7rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
text-align: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.gate-chip.active { border-color: var(--vp-c-brand-1); background: var(--vp-c-brand-soft); }
|
||||
.chip-name { font-size: 0.7rem; font-weight: bold; color: var(--vp-c-text-1); }
|
||||
.chip-rule { font-size: 0.62rem; color: var(--vp-c-text-3); }
|
||||
.chip-out { font-size: 0.7rem; font-family: monospace; margin-top: 0.15rem; }
|
||||
.gate-chip.active .chip-out strong { color: var(--vp-c-brand-1); }
|
||||
|
||||
.out-svg { width: 35px; height: 80px; flex-shrink: 0; }
|
||||
|
||||
.output-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.out-chip {
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
min-width: 5rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.out-chip strong { font-size: 1rem; display: block; }
|
||||
.out-chip.sum.active { background: #dcfce7; border-color: #16a34a; color: #166534; }
|
||||
.out-chip.carry.active { background: #fef3c7; border-color: #d97706; color: #92400e; }
|
||||
|
||||
.info-box strong {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.circuit-container {
|
||||
transform: scale(0.85);
|
||||
transform-origin: left top;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.terms-box {
|
||||
flex-direction: column;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.main-area { flex-direction: column; }
|
||||
.circuit-row { overflow-x: auto; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
placeholder="值 (如: 苹果)"
|
||||
class="hash-input"
|
||||
/>
|
||||
<button @click="addData" class="add-btn">添加</button>
|
||||
<button class="add-btn" @click="addData">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+9
-21
@@ -35,8 +35,7 @@
|
||||
v-for="lang in era.languages"
|
||||
:key="lang"
|
||||
class="lang-dot"
|
||||
>{{ lang }}</span
|
||||
>
|
||||
>{{ lang }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,8 +90,7 @@
|
||||
v-for="lang in selectedParadigm.languages"
|
||||
:key="lang"
|
||||
class="lang-tag"
|
||||
>{{ lang }}</span
|
||||
>
|
||||
>{{ lang }}</span>
|
||||
</div>
|
||||
<div class="paradigm-detail-example">
|
||||
<pre><code>{{ selectedParadigm.example }}</code></pre>
|
||||
@@ -102,8 +100,7 @@
|
||||
v-for="t in selectedParadigm.traits"
|
||||
:key="t"
|
||||
class="trait-chip"
|
||||
>{{ t }}</span
|
||||
>
|
||||
>{{ t }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,8 +158,7 @@
|
||||
v-for="lang in rec.langs"
|
||||
:key="lang"
|
||||
class="choose-lang-tag"
|
||||
>{{ lang }}</span
|
||||
>
|
||||
>{{ lang }}</span>
|
||||
</div>
|
||||
<div class="choose-reason">{{ rec.reason }}</div>
|
||||
</div>
|
||||
@@ -185,19 +181,11 @@
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>
|
||||
<span v-if="activeTab === 'timeline'"
|
||||
>编程语言从机器语言到现代高级语言,一直在朝着"更接近人类思维"的方向演化。</span
|
||||
>
|
||||
<span v-else-if="activeTab === 'paradigms'"
|
||||
>编程范式是思考问题的方式——命令式关注"怎么做",声明式关注"做什么",选择范式比选语言更重要。</span
|
||||
>
|
||||
<span v-else-if="activeTab === 'compare'"
|
||||
>没有最好的语言,只有最适合场景的语言。类型系统、运行方式、生态都是选择时的关键考量。</span
|
||||
>
|
||||
<span v-else
|
||||
>初学者先学 Python(简单通用),再学 JavaScript(Web
|
||||
必备),最后选一门静态语言(TypeScript/Go/Rust)深入。</span
|
||||
>
|
||||
<span v-if="activeTab === 'timeline'">编程语言从机器语言到现代高级语言,一直在朝着"更接近人类思维"的方向演化。</span>
|
||||
<span v-else-if="activeTab === 'paradigms'">编程范式是思考问题的方式——命令式关注"怎么做",声明式关注"做什么",选择范式比选语言更重要。</span>
|
||||
<span v-else-if="activeTab === 'compare'">没有最好的语言,只有最适合场景的语言。类型系统、运行方式、生态都是选择时的关键考量。</span>
|
||||
<span v-else>初学者先学 Python(简单通用),再学 JavaScript(Web
|
||||
必备),最后选一门静态语言(TypeScript/Go/Rust)深入。</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,373 +1,349 @@
|
||||
<template>
|
||||
<div class="memory-demo">
|
||||
<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="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 class="demo">
|
||||
<div class="title">🧠 操作系统给每个程序"画饼"</div>
|
||||
|
||||
<div class="scene">
|
||||
<!-- 程序视角 -->
|
||||
<div class="view-box">
|
||||
<div class="view-title">📱 程序以为的内存(虚拟)</div>
|
||||
<div class="virtual-mem">
|
||||
<div class="proc-mem wechat">
|
||||
<div class="proc-label">💬 微信</div>
|
||||
<div class="mem-blocks">
|
||||
<div
|
||||
v-for="n in 4"
|
||||
:key="n"
|
||||
class="v-block"
|
||||
:class="{ filled: wechatProgress >= n * 25 }"
|
||||
>{{ n }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="proc-mem game">
|
||||
<div class="proc-label">🎮 游戏</div>
|
||||
<div class="mem-blocks">
|
||||
<div
|
||||
v-for="n in 4"
|
||||
:key="n"
|
||||
class="v-block game"
|
||||
:class="{ filled: gameProgress >= n * 25 }"
|
||||
>{{ n }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OS 页表 (映射表) -->
|
||||
<div class="os-page-table">
|
||||
<div class="title">保安大叔 (OS 页表)</div>
|
||||
<div class="table-info">
|
||||
当程序存数据时,<br />由我暗中转移到真正的物理缝隙里。
|
||||
<!-- 映射箭头 -->
|
||||
<div class="mapping-arrow">
|
||||
<div class="arrow-text">操作系统偷偷映射 ↓</div>
|
||||
<div class="mapping-lines">
|
||||
<div
|
||||
v-for="(map, idx) in visibleMappings"
|
||||
:key="idx"
|
||||
class="map-line"
|
||||
:class="map.type"
|
||||
:style="{ animationDelay: idx * 0.2 + 's' }"
|
||||
>
|
||||
<span class="from">{{ map.from }}</span>
|
||||
<span class="line"></span>
|
||||
<span class="to">{{ map.to }}</span>
|
||||
</div>
|
||||
</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' }]"
|
||||
<div class="view-box physical">
|
||||
<div class="view-title">💾 真实的内存条(物理)</div>
|
||||
<div class="physical-mem">
|
||||
<div
|
||||
v-for="(block, idx) in physicalBlocks"
|
||||
:key="idx"
|
||||
class="p-block"
|
||||
:class="[block.type, { active: block.active }]"
|
||||
>
|
||||
{{ block.label }}
|
||||
<span class="p-addr">{{ idx + 1 }}</span>
|
||||
<span class="p-owner">{{ block.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation-box" v-if="wechatBlocks > 0 || gameBlocks > 0">
|
||||
💡
|
||||
发现了没?尽管右侧真正的物理内存已经被塞得像个狗皮膏药,但在左侧的微信和游戏眼里,自己的内存条永远是连续且干净的。更重要的是,微信绝对访问不到橘色的物理块,保证了安全!
|
||||
<div class="explain">
|
||||
<strong>💡 原理:</strong>每个程序以为自己独占连续的内存(左),实际上操作系统把数据分散存到真实内存各处(右)。程序看到的地址都是"假"的,操作系统负责翻译。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const wechatBlocks = ref(0)
|
||||
const gameBlocks = ref(0)
|
||||
const wechatProgress = ref(0)
|
||||
const gameProgress = ref(0)
|
||||
const currentMapping = ref(0)
|
||||
|
||||
// 初始物理内存状态,模拟碎片化
|
||||
// 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 physicalBlocks = ref([
|
||||
{ type: 'os', label: '系统', active: false },
|
||||
{ type: 'empty', label: '', active: false },
|
||||
{ type: 'empty', label: '', active: false },
|
||||
{ type: 'os', label: '系统', active: false },
|
||||
{ type: 'empty', label: '', active: false },
|
||||
{ type: 'empty', label: '', active: false },
|
||||
{ type: 'empty', label: '', active: false },
|
||||
{ type: 'os', label: '系统', active: false }
|
||||
])
|
||||
|
||||
// 映射关系(虚拟地址 -> 物理地址)
|
||||
const mappings = [
|
||||
{ from: '微信-1', to: '物理-2', type: 'wechat' },
|
||||
{ from: '微信-2', to: '物理-3', type: 'wechat' },
|
||||
{ from: '游戏-1', to: '物理-5', type: 'game' },
|
||||
{ from: '游戏-2', to: '物理-6', type: 'game' }
|
||||
]
|
||||
|
||||
const physicalBlocks = ref(JSON.parse(JSON.stringify(initialPhysicalBlocks)))
|
||||
|
||||
const freeSpaceCount = computed(() => {
|
||||
return physicalBlocks.value.filter((b) => b.type === 'empty').length
|
||||
const visibleMappings = computed(() => {
|
||||
return mappings.slice(0, currentMapping.value)
|
||||
})
|
||||
|
||||
const hasFreeSpace = computed(() => freeSpaceCount.value > 0)
|
||||
let timer = null
|
||||
let phase = 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 runDemo = () => {
|
||||
switch(phase) {
|
||||
case 0: // 微信申请内存
|
||||
wechatProgress.value = 50
|
||||
physicalBlocks.value[1] = { type: 'wechat', label: 'W1', active: true }
|
||||
physicalBlocks.value[2] = { type: 'wechat', label: 'W2', active: true }
|
||||
currentMapping.value = 2
|
||||
phase = 1
|
||||
break
|
||||
case 1: // 游戏申请内存
|
||||
gameProgress.value = 50
|
||||
physicalBlocks.value[4] = { type: 'game', label: 'G1', active: true }
|
||||
physicalBlocks.value[5] = { type: 'game', label: 'G2', active: true }
|
||||
currentMapping.value = 4
|
||||
phase = 2
|
||||
break
|
||||
case 2: // 高亮显示
|
||||
physicalBlocks.value.forEach(b => b.active = false)
|
||||
phase = 3
|
||||
break
|
||||
case 3: // 重置
|
||||
wechatProgress.value = 0
|
||||
gameProgress.value = 0
|
||||
currentMapping.value = 0
|
||||
physicalBlocks.value[1] = { type: 'empty', label: '', active: false }
|
||||
physicalBlocks.value[2] = { type: 'empty', label: '', active: false }
|
||||
physicalBlocks.value[4] = { type: 'empty', label: '', active: false }
|
||||
physicalBlocks.value[5] = { type: 'empty', label: '', active: false }
|
||||
phase = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const fillRandomEmptyBlock = (type, label) => {
|
||||
const emptyIndices = []
|
||||
physicalBlocks.value.forEach((b, i) => {
|
||||
if (b.type === 'empty') emptyIndices.push(i)
|
||||
})
|
||||
onMounted(() => {
|
||||
timer = setInterval(runDemo, 2000)
|
||||
})
|
||||
|
||||
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))
|
||||
}
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.memory-demo {
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
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-controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.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;
|
||||
align-items: stretch;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.system-view {
|
||||
flex-direction: column;
|
||||
}
|
||||
padding: 16px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--vp-c-text-1);
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.scene {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.block {
|
||||
padding: 0.6rem;
|
||||
.view-box {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.view-box.physical {
|
||||
background: #1a1a2e11;
|
||||
}
|
||||
|
||||
.view-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.virtual-mem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.proc-mem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.proc-label {
|
||||
font-size: 11px;
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mem-blocks {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.v-block {
|
||||
flex: 1;
|
||||
height: 28px;
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
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;
|
||||
.v-block.filled {
|
||||
background: #16a34a33;
|
||||
border: 1px solid #16a34a;
|
||||
color: #16a34a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.os-page-table {
|
||||
flex: 1;
|
||||
.v-block.game.filled {
|
||||
background: #d9770633;
|
||||
border-color: #d97706;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.mapping-arrow {
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.arrow-text {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mapping-lines {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.map-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 10px;
|
||||
animation: fade-in 0.3s ease;
|
||||
}
|
||||
|
||||
.map-line.wechat {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.map-line.game {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.map-line .line {
|
||||
width: 20px;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.physical-mem {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.p-block {
|
||||
height: 32px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
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);
|
||||
font-size: 9px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.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.8rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.physical-memory {
|
||||
flex: 1;
|
||||
background: var(--vp-c-bg-alt);
|
||||
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);
|
||||
.p-block.os {
|
||||
background: var(--vp-c-bg-soft);
|
||||
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;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
0% {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
.p-block.wechat {
|
||||
background: #16a34a22;
|
||||
border-color: #16a34a;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.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;
|
||||
.p-block.game {
|
||||
background: #d9770622;
|
||||
border-color: #d97706;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.p-block.active {
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.p-addr {
|
||||
font-size: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.p-owner {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.explain {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
padding: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.explain strong { color: var(--vp-c-text-1); }
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,8 +70,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong
|
||||
>分层设计让网络协议模块化,每层只关心自己的职责。数据从应用层向下传递时,每层都会添加自己的"信封"(头部),接收时再逐层拆开。
|
||||
<strong>核心思想:</strong>分层设计让网络协议模块化,每层只关心自己的职责。数据从应用层向下传递时,每层都会添加自己的"信封"(头部),接收时再逐层拆开。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="scene">
|
||||
<!-- 应用程序层 -->
|
||||
<div class="layer-box app-layer" :class="{ active: currentStep >= 1 }">
|
||||
<div class="layer-title">📱 应用程序</div>
|
||||
<div class="apps">
|
||||
<span class="app-icon" :class="{ pulse: currentStep === 1 }">🎵</span>
|
||||
<span class="app-icon" :class="{ pulse: currentStep === 1 }">💬</span>
|
||||
<span class="app-icon" :class="{ pulse: currentStep === 1 }">🎮</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流动箭头 -->
|
||||
<div class="flow-arrow" :class="{ flowing: currentStep === 2 }">
|
||||
<div class="arrow-line"></div>
|
||||
<div class="arrow-head">▼</div>
|
||||
<div class="packet" v-if="currentStep === 2">📦 请求</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作系统层 -->
|
||||
<div class="layer-box os-layer" :class="{ active: currentStep >= 2, processing: currentStep === 3 }">
|
||||
<div class="layer-title">🖥️ 操作系统</div>
|
||||
<div class="os-core">
|
||||
<div class="core-item" :class="{ working: currentStep === 3 && subStep === 0 }">调度CPU</div>
|
||||
<div class="core-item" :class="{ working: currentStep === 3 && subStep === 1 }">分配内存</div>
|
||||
<div class="core-item" :class="{ working: currentStep === 3 && subStep === 2 }">管理文件</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流动箭头 -->
|
||||
<div class="flow-arrow" :class="{ flowing: currentStep === 4 }">
|
||||
<div class="arrow-line"></div>
|
||||
<div class="arrow-head">▼</div>
|
||||
<div class="packet" v-if="currentStep === 4">⚡ 指令</div>
|
||||
</div>
|
||||
|
||||
<!-- 硬件层 -->
|
||||
<div class="layer-box hw-layer" :class="{ active: currentStep >= 4, working: currentStep === 5 }">
|
||||
<div class="layer-title">💾 硬件</div>
|
||||
<div class="hw-items">
|
||||
<span class="hw-icon" :class="{ spin: currentStep === 5 }">🧠 CPU</span>
|
||||
<span class="hw-icon" :class="{ flash: currentStep === 5 }">💾 内存</span>
|
||||
<span class="hw-icon" :class="{ flash: currentStep === 5 }">💿 硬盘</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-bar">
|
||||
<span class="status-text">{{ statusText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const currentStep = ref(0)
|
||||
const subStep = ref(0)
|
||||
let timer = null
|
||||
|
||||
const statusTexts = [
|
||||
'应用程序准备发起请求...',
|
||||
'应用程序:我要播放音乐!',
|
||||
'请求发送给操作系统...',
|
||||
'操作系统正在协调资源...',
|
||||
'指令下发到硬件...',
|
||||
'硬件开始执行:音乐播放中 🎵'
|
||||
]
|
||||
|
||||
const statusText = computed(() => statusTexts[currentStep.value] || '')
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentStep.value === 3) {
|
||||
// 在操作系统处理阶段,循环显示子步骤
|
||||
subStep.value = (subStep.value + 1) % 3
|
||||
if (subStep.value === 0) {
|
||||
currentStep.value = 4
|
||||
}
|
||||
} else {
|
||||
currentStep.value = (currentStep.value + 1) % 6
|
||||
if (currentStep.value === 3) {
|
||||
subStep.value = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(nextStep, 1500)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.scene {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.layer-box {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.3s;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.layer-box.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.app-layer {
|
||||
background: linear-gradient(135deg, #667eea22, #764ba222);
|
||||
border-color: #667eea55;
|
||||
}
|
||||
|
||||
.app-layer.active {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 15px #667eea55;
|
||||
}
|
||||
|
||||
.os-layer {
|
||||
background: linear-gradient(135deg, #f093fb22, #f5576c22);
|
||||
border-color: #f5576c55;
|
||||
}
|
||||
|
||||
.os-layer.active {
|
||||
border-color: #f5576c;
|
||||
box-shadow: 0 0 15px #f5576c55;
|
||||
}
|
||||
|
||||
.os-layer.processing {
|
||||
animation: pulse-os 1s infinite;
|
||||
}
|
||||
|
||||
.hw-layer {
|
||||
background: linear-gradient(135deg, #4facfe22, #00f2fe22);
|
||||
border-color: #4facfe55;
|
||||
}
|
||||
|
||||
.hw-layer.active {
|
||||
border-color: #4facfe;
|
||||
box-shadow: 0 0 15px #4facfe55;
|
||||
}
|
||||
|
||||
.hw-layer.working {
|
||||
animation: pulse-hw 0.5s infinite;
|
||||
}
|
||||
|
||||
.layer-title {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.apps {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
font-size: 24px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.app-icon.pulse {
|
||||
animation: bounce 0.5s infinite;
|
||||
}
|
||||
|
||||
.os-core {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.core-item {
|
||||
padding: 6px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.core-item.working {
|
||||
background: #f5576c;
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.hw-items {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hw-icon {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.hw-icon.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.hw-icon.flash {
|
||||
animation: flash 0.5s infinite;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arrow-line {
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.flow-arrow.flowing .arrow-line {
|
||||
background: linear-gradient(to bottom, #f5576c, #4facfe);
|
||||
box-shadow: 0 0 5px #f5576c;
|
||||
}
|
||||
|
||||
.arrow-head {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-divider);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.flow-arrow.flowing .arrow-head {
|
||||
color: #4facfe;
|
||||
}
|
||||
|
||||
.packet {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #f5576c;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
animation: flow-down 1s ease-in-out;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
margin-top: 12px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
}
|
||||
|
||||
@keyframes pulse-os {
|
||||
0%, 100% { box-shadow: 0 0 5px #f5576c55; }
|
||||
50% { box-shadow: 0 0 20px #f5576caa; }
|
||||
}
|
||||
|
||||
@keyframes pulse-hw {
|
||||
0%, 100% { box-shadow: 0 0 5px #4facfe55; }
|
||||
50% { box-shadow: 0 0 20px #4facfeaa; }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
@keyframes flow-down {
|
||||
0% { opacity: 0; transform: translate(-50%, -100%); }
|
||||
20% { opacity: 1; }
|
||||
80% { opacity: 1; }
|
||||
100% { opacity: 0; transform: translate(-50%, 0%); }
|
||||
}
|
||||
</style>
|
||||
-313
@@ -1,313 +0,0 @@
|
||||
<template>
|
||||
<div class="os-overview-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">操作系统:计算机的"大管家"</span>
|
||||
<span class="subtitle">让多个程序和谐共处的艺术</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="os-layers">
|
||||
<div class="layer user-apps">
|
||||
<div class="layer-title">应用程序层</div>
|
||||
<div class="layer-content">
|
||||
<div
|
||||
v-for="app in applications"
|
||||
:key="app.id"
|
||||
class="app-icon"
|
||||
:class="{ active: activeApp === app.id }"
|
||||
@click="activeApp = app.id"
|
||||
:title="app.name"
|
||||
>
|
||||
{{ app.icon }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-desc">
|
||||
用户直接使用的程序(浏览器、IDE、游戏等)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layer kernel">
|
||||
<div class="layer-title">操作系统内核</div>
|
||||
<div class="layer-content">
|
||||
<div class="kernel-components">
|
||||
<div
|
||||
v-for="component in kernelComponents"
|
||||
:key="component.id"
|
||||
class="kernel-component"
|
||||
:class="{ active: activeComponent === component.id }"
|
||||
@click="activeComponent = component.id"
|
||||
>
|
||||
{{ component.icon }} {{ component.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-desc">进程管理、内存管理、文件系统、设备管理</div>
|
||||
</div>
|
||||
|
||||
<div class="layer hardware">
|
||||
<div class="layer-title">硬件层</div>
|
||||
<div class="layer-content">
|
||||
<div class="hardware-icons">
|
||||
<span>💻 CPU</span>
|
||||
<span>🧠 RAM</span>
|
||||
<span>💾 硬盘</span>
|
||||
<span>🖥️ GPU</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-flow">
|
||||
<div class="flow-title">资源流向</div>
|
||||
<div class="flow-content">
|
||||
<div class="flow-item" :class="{ active: showFlow }">
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-desc">应用程序请求资源(内存、CPU、文件)</div>
|
||||
</div>
|
||||
<div class="flow-item" :class="{ active: showFlow }">
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-desc">操作系统内核统一分配和调度</div>
|
||||
</div>
|
||||
<div class="flow-item" :class="{ active: showFlow }">
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-desc">硬件执行实际操作</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="flow-btn" @click="showFlow = !showFlow">
|
||||
{{ showFlow ? '隐藏' : '显示' }}资源流
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-details">
|
||||
<div class="detail-item">
|
||||
<div class="detail-title">
|
||||
当前选中:{{ activeAppName || '请选择应用程序' }}
|
||||
</div>
|
||||
<div class="detail-desc">
|
||||
{{ getActiveAppDesc() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activeApp = ref('browser')
|
||||
const activeComponent = ref('process')
|
||||
const showFlow = ref(false)
|
||||
|
||||
const applications = [
|
||||
{ id: 'browser', name: '浏览器', icon: '🌐' },
|
||||
{ id: 'ide', name: '代码编辑器', icon: '💻' },
|
||||
{ id: 'music', name: '音乐播放器', icon: '🎵' },
|
||||
{ id: 'video', name: '视频播放器', icon: '🎬' },
|
||||
{ id: 'game', name: '游戏', icon: '🎮' }
|
||||
]
|
||||
|
||||
const kernelComponents = [
|
||||
{ id: 'process', name: '进程管理', icon: '🔄' },
|
||||
{ id: 'memory', name: '内存管理', icon: '🧠' },
|
||||
{ id: 'filesystem', name: '文件系统', icon: '📁' },
|
||||
{ id: 'device', name: '设备管理', icon: '🔧' }
|
||||
]
|
||||
|
||||
const activeAppName = computed(() => {
|
||||
const app = applications.find((a) => a.id === activeApp.value)
|
||||
return app?.name || ''
|
||||
})
|
||||
|
||||
const getActiveAppDesc = () => {
|
||||
const component = kernelComponents.find((c) => c.id === activeComponent.value)
|
||||
const app = applications.find((a) => a.id === activeApp.value)
|
||||
|
||||
if (!app || !component) return '请选择应用程序和内核组件'
|
||||
return `${app.name} 需要使用 ${component.name} 的功能`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.os-overview-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.os-layers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.layer {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.layer.user-apps {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.layer.kernel {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.layer.hardware {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.layer-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.layer-content {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
font-size: 1.8rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.app-icon:hover,
|
||||
.app-icon.active {
|
||||
transform: scale(1.1);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.kernel-component {
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.kernel-component:hover,
|
||||
.kernel-component.active {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hardware-icons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 1.2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.layer-desc {
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.resource-flow {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.flow-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.flow-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flow-item {
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.5rem;
|
||||
opacity: 0.6;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.flow-item.active {
|
||||
opacity: 1;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.flow-btn {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.flow-btn:hover {
|
||||
background: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.demo-details {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--vP-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@@ -1,376 +1,259 @@
|
||||
<template>
|
||||
<div class="process-demo">
|
||||
<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 class="demo">
|
||||
<div class="title">⏱️ CPU 在疯狂切换,你感觉不出来</div>
|
||||
|
||||
<div class="cpu-core">
|
||||
<div class="cpu-label">CPU</div>
|
||||
<div class="current-task" :class="{ switching: isSwitching }">
|
||||
<span class="task-icon">{{ currentTask.icon }}</span>
|
||||
<span class="task-name">{{ currentTask.name }}</span>
|
||||
</div>
|
||||
<div class="time-slice">时间片: {{ timeLeft }}ms</div>
|
||||
</div>
|
||||
|
||||
<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
|
||||
class="data-flow"
|
||||
:class="[`flow-${activeProcessId}`, { running: isRunning }]"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="processes-grid">
|
||||
<div class="process-queue">
|
||||
<div
|
||||
v-for="p in processes"
|
||||
:key="p.id"
|
||||
class="process-card"
|
||||
:class="{ active: p.id === activeProcessId }"
|
||||
v-for="(proc, idx) in processes"
|
||||
:key="proc.id"
|
||||
class="process"
|
||||
:class="{
|
||||
active: idx === currentIdx,
|
||||
waiting: idx !== currentIdx,
|
||||
done: proc.progress >= 100
|
||||
}"
|
||||
:style="{ '--progress': proc.progress + '%' }"
|
||||
>
|
||||
<div class="p-header">
|
||||
<div class="p-title">
|
||||
<span class="icon">{{ p.icon }}</span>
|
||||
<span class="name">{{ p.name }}</span>
|
||||
<span class="p-icon">{{ proc.icon }}</span>
|
||||
<div class="p-info">
|
||||
<span class="p-name">{{ proc.name }}</span>
|
||||
<div class="p-bar">
|
||||
<div class="p-fill"></div>
|
||||
</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>
|
||||
<span class="p-status">{{ idx === currentIdx ? '运行中' : (proc.progress >= 100 ? '完成' : '等待') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="explanation-box"
|
||||
:class="{ show: isRunning && speed === 'fast' }"
|
||||
>
|
||||
💡
|
||||
**关键启示**:当切换速度足够快时,肉眼已经无法分辨谁在“等待”。这也就是为什么只有一个
|
||||
CPU 核心的电脑,依然能让你一边听歌一边打字!
|
||||
<div class="explain">
|
||||
<strong>💡 原理:</strong>CPU 每 {{ sliceTime }}ms 切换一次进程,因为太快了你感觉是"同时运行"。实际上每个进程都在断断续续地执行。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
|
||||
const isRunning = ref(false)
|
||||
const activeProcessId = ref(null)
|
||||
const speed = ref('slow')
|
||||
let interval = null
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const processes = ref([
|
||||
{ id: 1, name: '微信接收', icon: '💬', progress: 0 },
|
||||
{ id: 2, name: '音乐播放', icon: '🎵', progress: 0 },
|
||||
{ id: 3, name: '游戏渲染', icon: '🎮', progress: 0 }
|
||||
{ id: 1, name: '微信', icon: '💬', progress: 0 },
|
||||
{ id: 2, name: '音乐', icon: '🎵', progress: 0 },
|
||||
{ id: 3, name: '浏览器', icon: '🌐', progress: 0 }
|
||||
])
|
||||
|
||||
const activeProcess = computed(() =>
|
||||
processes.value.find((p) => p.id === activeProcessId.value)
|
||||
)
|
||||
const currentIdx = ref(0)
|
||||
const timeLeft = ref(0)
|
||||
const isSwitching = ref(false)
|
||||
const sliceTime = 100 // 每个时间片100ms(演示用,实际是10ms左右)
|
||||
|
||||
const setSpeed = (s) => {
|
||||
speed.value = s
|
||||
if (isRunning.value) {
|
||||
clearInterval(interval)
|
||||
startLoop()
|
||||
let timer = null
|
||||
let switchTimer = null
|
||||
|
||||
const switchTask = () => {
|
||||
isSwitching.value = true
|
||||
setTimeout(() => {
|
||||
currentIdx.value = (currentIdx.value + 1) % processes.value.length
|
||||
timeLeft.value = sliceTime
|
||||
isSwitching.value = false
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
const current = processes.value[currentIdx.value]
|
||||
|
||||
// 当前进程执行
|
||||
if (current.progress < 100) {
|
||||
current.progress = Math.min(100, current.progress + 5)
|
||||
}
|
||||
|
||||
// 时间片倒计时
|
||||
timeLeft.value -= 10
|
||||
|
||||
// 时间片用完,切换
|
||||
if (timeLeft.value <= 0) {
|
||||
switchTask()
|
||||
}
|
||||
|
||||
// 检查是否全部完成
|
||||
if (processes.value.every(p => p.progress >= 100)) {
|
||||
// 重置演示
|
||||
setTimeout(() => {
|
||||
processes.value.forEach(p => p.progress = 0)
|
||||
currentIdx.value = 0
|
||||
timeLeft.value = sliceTime
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
timeLeft.value = sliceTime
|
||||
timer = setInterval(tick, 10) // 每10ms更新一次
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (interval) clearInterval(interval)
|
||||
clearInterval(timer)
|
||||
clearTimeout(switchTimer)
|
||||
})
|
||||
|
||||
const currentTask = computed(() => processes.value[currentIdx.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.process-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
font-family: var(--vp-font-family-base);
|
||||
}
|
||||
|
||||
.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;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
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;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.speed-control button.active {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
border-color: var(--vp-c-brand-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cpu-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
background: linear-gradient(135deg, #667eea22, #764ba222);
|
||||
border: 2px solid #667eea;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
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;
|
||||
|
||||
.cpu-label {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.current-task {
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.task-badge {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: white;
|
||||
padding: 0.2rem 0.8rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.task-badge.idle {
|
||||
background: var(--vp-c-text-3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* 连接线动画占位,简化效果,用发亮的虚线替代 */
|
||||
.connector {
|
||||
width: 2px;
|
||||
height: 30px;
|
||||
background: var(--vp-c-divider);
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
.current-task.switching {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.processes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
.task-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@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: '';
|
||||
.time-slice {
|
||||
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;
|
||||
top: 8px;
|
||||
right: 12px;
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-bg);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.status-badge.waiting {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
.status-badge.running {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: var(--vp-c-success-1);
|
||||
}
|
||||
|
||||
.p-progress {
|
||||
.process-queue {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
.process {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.process.active {
|
||||
border-color: #667eea;
|
||||
background: #667eea11;
|
||||
box-shadow: 0 0 10px #667eea33;
|
||||
}
|
||||
|
||||
.process.done {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.process.done .p-fill {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.p-icon {
|
||||
font-size: 20px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.p-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.p-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.p-bar {
|
||||
height: 4px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-fill {
|
||||
|
||||
.p-fill {
|
||||
height: 100%;
|
||||
background: var(--vp-c-brand-1);
|
||||
width: var(--progress);
|
||||
background: #667eea;
|
||||
border-radius: 2px;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
.process-card.active .progress-fill {
|
||||
background: var(--vp-c-success-1);
|
||||
|
||||
.p-status {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
padding: 2px 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.75rem;
|
||||
.process.active .p-status {
|
||||
color: #667eea;
|
||||
background: #667eea22;
|
||||
}
|
||||
|
||||
.explain {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.5;
|
||||
padding: 10px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
.explain strong { color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
|
||||
-446
@@ -1,446 +0,0 @@
|
||||
<template>
|
||||
<div class="pmf-collab-demo">
|
||||
<div class="demo-header">
|
||||
<span class="title">进程、内存、文件系统的协作</span>
|
||||
<span class="subtitle">三大管理模块如何协同工作</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="collab-scene">
|
||||
<div class="scene-title">场景选择:</div>
|
||||
<div class="scene-buttons">
|
||||
<button
|
||||
v-for="scene in scenes"
|
||||
:key="scene.id"
|
||||
:class="['scene-btn', { active: activeScene === scene.id }]"
|
||||
@click="activeScene = scene.id"
|
||||
>
|
||||
{{ scene.icon }} {{ scene.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collab-visualization">
|
||||
<div class="vis-container">
|
||||
<!-- 进程区域 -->
|
||||
<div class="zone process-zone">
|
||||
<div class="zone-header">
|
||||
<span class="zone-icon">🔄</span>
|
||||
<span class="zone-name">进程管理</span>
|
||||
</div>
|
||||
<div class="zone-content">
|
||||
<div class="process-list">
|
||||
<div
|
||||
v-for="proc in processes"
|
||||
:key="proc.id"
|
||||
class="process-item"
|
||||
:class="{ active: proc.id === currentProcessId }"
|
||||
>
|
||||
<span class="proc-name">{{ proc.name }}</span>
|
||||
<span class="proc-pid">PID: {{ proc.pid }}</span>
|
||||
<span class="proc-state">{{ proc.state }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内存区域 -->
|
||||
<div class="zone memory-zone">
|
||||
<div class="zone-header">
|
||||
<span class="zone-icon">🧠</span>
|
||||
<span class="zone-name">内存管理</span>
|
||||
</div>
|
||||
<div class="zone-content">
|
||||
<div class="memory-grid">
|
||||
<div
|
||||
v-for="(block, index) in memoryBlocks"
|
||||
:key="index"
|
||||
class="memory-block"
|
||||
:class="{
|
||||
allocated: block.allocated,
|
||||
process: block.processId
|
||||
}"
|
||||
:title="`地址: ${block.address}, 大小: ${block.size}KB`"
|
||||
>
|
||||
<div v-if="block.allocated" class="block-info">
|
||||
{{ getProcessName(block.processId) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件系统区域 -->
|
||||
<div class="zone filesystem-zone">
|
||||
<div class="zone-header">
|
||||
<span class="zone-icon">📁</span>
|
||||
<span class="zone-name">文件系统</span>
|
||||
</div>
|
||||
<div class="zone-content">
|
||||
<div class="file-tree">
|
||||
<div
|
||||
v-for="file in files"
|
||||
:key="file.id"
|
||||
class="file-item"
|
||||
:class="{ active: file.id === currentFileId }"
|
||||
>
|
||||
<span class="file-icon">{{ getIcon(file.type) }}</span>
|
||||
<span class="file-name">{{ file.name }}</span>
|
||||
<span v-if="file.size" class="file-size">{{
|
||||
file.size
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<div class="exp-title">
|
||||
{{ currentSceneData.title }}
|
||||
</div>
|
||||
<div class="exp-content">
|
||||
<div
|
||||
v-for="(step, index) in currentSceneData.steps"
|
||||
:key="index"
|
||||
class="exp-step"
|
||||
>
|
||||
<span class="step-number">{{ index + 1 }}</span>
|
||||
<span class="step-text">{{ step }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activeScene = ref('launch')
|
||||
const currentProcessId = ref(null)
|
||||
const currentFileId = ref(null)
|
||||
|
||||
const scenes = [
|
||||
{
|
||||
id: 'launch',
|
||||
name: '启动程序',
|
||||
icon: '🚀'
|
||||
},
|
||||
{
|
||||
id: 'memory-access',
|
||||
name: '内存访问',
|
||||
icon: '💾'
|
||||
},
|
||||
{
|
||||
id: 'file-access',
|
||||
name: '文件读写',
|
||||
icon: '📄'
|
||||
},
|
||||
{
|
||||
id: 'context-switch',
|
||||
name: '进程切换',
|
||||
icon: '🔄'
|
||||
}
|
||||
]
|
||||
|
||||
const processes = ref([
|
||||
{ id: 1, name: '浏览器', pid: 1001, state: '运行中' },
|
||||
{ id: 2, name: '音乐播放器', pid: 1002, state: '等待中' },
|
||||
{ id: 3, name: '代码编辑器', pid: 1003, state: '运行中' }
|
||||
])
|
||||
|
||||
const memoryBlocks = ref([
|
||||
{ address: '0x1000', size: 256, allocated: true, processId: 1 },
|
||||
{ address: '0x2000', size: 128, allocated: true, processId: 2 },
|
||||
{ address: '0x3000', size: 512, allocated: true, processId: 3 },
|
||||
{ address: '0x4000', size: 1024, allocated: false, processId: null },
|
||||
{ address: '0x5000', size: 512, allocated: false, processId: null },
|
||||
{ address: '0x6000', size: 256, allocated: false, processId: null },
|
||||
{ address: '0x7000', size: 128, allocated: false, processId: null }
|
||||
])
|
||||
|
||||
const files = ref([
|
||||
{ id: 1, name: 'config.json', type: 'json', size: '2KB' },
|
||||
{ id: 2, name: 'user_data.db', type: 'db', size: '50MB' },
|
||||
{ id: 3, name: 'cache', type: 'folder', size: '' },
|
||||
{ id: 4, name: 'song.mp3', type: 'audio', size: '5MB' }
|
||||
])
|
||||
|
||||
const sceneData = {
|
||||
launch: {
|
||||
title: '场景1:启动程序(浏览器)',
|
||||
steps: [
|
||||
'1. 用户双击浏览器图标',
|
||||
'2. 进程管理创建新进程(PID: 1004)',
|
||||
'3. 内存管理分配内存空间(代码段、数据段、堆、栈)',
|
||||
'4. 文件系统读取配置文件和缓存数据'
|
||||
]
|
||||
},
|
||||
'memory-access': {
|
||||
title: '场景2:程序运行时申请内存',
|
||||
steps: [
|
||||
'1. 浏览器加载大图片,需要更多内存',
|
||||
'2. 进程通过系统调用请求内存(malloc)',
|
||||
'3. 内存管理查找可用内存块(如:0x4000)',
|
||||
'4. 将内存块标记为"已分配",返回地址给程序'
|
||||
]
|
||||
},
|
||||
'file-access': {
|
||||
title: '场景3:保存文件',
|
||||
steps: [
|
||||
'1. 用户在浏览器点击"保存图片"',
|
||||
'2. 进程发起文件写入系统调用',
|
||||
'3. 文件系统查找空闲磁盘空间',
|
||||
'4. 将数据写入磁盘,更新文件分配表'
|
||||
]
|
||||
},
|
||||
'context-switch': {
|
||||
title: '场景4:切换到音乐播放器',
|
||||
steps: [
|
||||
'1. 用户点击音乐播放器窗口',
|
||||
'2. 操作系统暂停浏览器进程',
|
||||
'3. 调度器加载音乐播放器进程上下文',
|
||||
'4. CPU开始执行音乐播放器代码'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const currentSceneData = computed(
|
||||
() => sceneData[activeScene.value] || sceneData.launch
|
||||
)
|
||||
|
||||
const getProcessName = (id) => {
|
||||
const proc = processes.value.find((p) => p.id === id)
|
||||
return proc?.name || '系统'
|
||||
}
|
||||
|
||||
const getIcon = (type) => {
|
||||
const icons = {
|
||||
json: '📋',
|
||||
db: '🗄️',
|
||||
folder: '📁',
|
||||
audio: '🎵'
|
||||
}
|
||||
return icons[type] || '📄'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pmf-collab-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.scene-buttons {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.scene-btn {
|
||||
padding: 0.6rem 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.scene-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.scene-btn.active {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.collab-visualization {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.collab-visualization {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.zone {
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.zone-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.zone-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.zone-name {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.process-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.process-item.active {
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.proc-name,
|
||||
.proc-pid,
|
||||
.proc-state {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proc-state {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.memory-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.memory-block {
|
||||
aspect-ratio: 2;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
position: relative;
|
||||
background: var(--vp-c-bg-soft);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.memory-block.allocated {
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.memory-block.process {
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.block-info {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-tree {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.file-item.active {
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
flex: 1;
|
||||
}
|
||||
.file-size {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.explanation {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.exp-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.exp-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.exp-step {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
color: var(--vp-c-brand);
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="title">🚀 双击图标后,电脑在忙什么?</div>
|
||||
|
||||
<div class="timeline">
|
||||
<div
|
||||
v-for="(step, idx) in steps"
|
||||
:key="idx"
|
||||
class="step"
|
||||
:class="{
|
||||
done: currentStep > idx,
|
||||
active: currentStep === idx,
|
||||
pending: currentStep < idx
|
||||
}"
|
||||
>
|
||||
<div class="step-marker">
|
||||
<span class="step-num">{{ idx + 1 }}</span>
|
||||
<span class="step-icon">{{ step.icon }}</span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-title">{{ step.title }}</div>
|
||||
<div class="step-desc" v-if="currentStep === idx">{{ step.desc }}</div>
|
||||
</div>
|
||||
<div class="step-arrow" v-if="idx < steps.length - 1">→</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="visualization" v-if="currentStep >= 0">
|
||||
<div class="viz-box" :class="vizClass">
|
||||
<div class="viz-icon">{{ currentViz.icon }}</div>
|
||||
<div class="viz-text">{{ currentViz.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: progressPercent + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const steps = [
|
||||
{
|
||||
icon: '👆',
|
||||
title: '你双击图标',
|
||||
desc: '操作系统收到"启动浏览器"的请求'
|
||||
},
|
||||
{
|
||||
icon: '📋',
|
||||
title: '创建进程',
|
||||
desc: '建立"户口本",记录进程ID和状态'
|
||||
},
|
||||
{
|
||||
icon: '🧠',
|
||||
title: '分配内存',
|
||||
desc: '划分虚拟内存空间,让程序以为独占内存'
|
||||
},
|
||||
{
|
||||
icon: '📁',
|
||||
title: '加载文件',
|
||||
desc: '从硬盘读取程序代码到内存'
|
||||
},
|
||||
{
|
||||
icon: '▶️',
|
||||
title: '开始运行',
|
||||
desc: 'CPU开始执行,窗口出现在屏幕上!'
|
||||
}
|
||||
]
|
||||
|
||||
const vizStates = [
|
||||
{ icon: '🖱️', text: '点击中...' },
|
||||
{ icon: '📋', text: '创建进程...' },
|
||||
{ icon: '💾', text: '分配内存...' },
|
||||
{ icon: '💿', text: '读取文件...' },
|
||||
{ icon: '🖥️', text: '运行中!' }
|
||||
]
|
||||
|
||||
const currentStep = ref(0)
|
||||
let timer = null
|
||||
|
||||
const vizClass = computed(() => {
|
||||
const classes = ['click', 'process', 'memory', 'file', 'run']
|
||||
return classes[currentStep.value] || ''
|
||||
})
|
||||
|
||||
const currentViz = computed(() => vizStates[currentStep.value] || vizStates[0])
|
||||
|
||||
const progressPercent = computed(() => {
|
||||
return ((currentStep.value + 1) / steps.length) * 100
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
currentStep.value = (currentStep.value + 1) % steps.length
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-marker {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 6px;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.step.done .step-marker {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step.active .step-marker {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
animation: pulse 1s infinite;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.step.pending .step-marker {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.step-content {
|
||||
text-align: center;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.step.done .step-title {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.step.active .step-title {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.step.pending .step-title {
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 9px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.3;
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.step-arrow {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.visualization {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.viz-box {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 32px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.viz-box.click {
|
||||
background: #667eea22;
|
||||
border: 2px solid #667eea;
|
||||
}
|
||||
|
||||
.viz-box.process {
|
||||
background: #f093fb22;
|
||||
border: 2px solid #f5576c;
|
||||
}
|
||||
|
||||
.viz-box.memory {
|
||||
background: #4facfe22;
|
||||
border: 2px solid #4facfe;
|
||||
}
|
||||
|
||||
.viz-box.file {
|
||||
background: #fa709a22;
|
||||
border: 2px solid #fa709a;
|
||||
}
|
||||
|
||||
.viz-box.run {
|
||||
background: #10b98122;
|
||||
border: 2px solid #10b981;
|
||||
animation: success 0.5s ease;
|
||||
}
|
||||
|
||||
.viz-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.viz-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 4px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), #10b981);
|
||||
border-radius: 2px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 var(--vp-c-brand-soft); }
|
||||
50% { box-shadow: 0 0 0 8px transparent; }
|
||||
}
|
||||
|
||||
@keyframes success {
|
||||
0% { transform: scale(0.9); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
</style>
|
||||
+1
-2
@@ -110,8 +110,7 @@ function traverse(folder) {
|
||||
traverse(item) // 递归调用!
|
||||
}
|
||||
}
|
||||
}</pre
|
||||
>
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
<span
|
||||
class="val-box"
|
||||
:class="{ on: storedData === 1, flash: isWriting }"
|
||||
>{{ storedData }}</span
|
||||
>
|
||||
>{{ storedData }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Output -->
|
||||
|
||||
+4
-4
@@ -40,10 +40,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-controls">
|
||||
<button @click="startLinearSearch" class="search-btn">
|
||||
<button class="search-btn" @click="startLinearSearch">
|
||||
开始查找
|
||||
</button>
|
||||
<button @click="reset" class="reset-btn">重置</button>
|
||||
<button class="reset-btn" @click="reset">重置</button>
|
||||
</div>
|
||||
<div class="search-info">
|
||||
目标数字:<input
|
||||
@@ -89,8 +89,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-controls">
|
||||
<button @click="binaryStep" class="search-btn">下一步</button>
|
||||
<button @click="resetBinary" class="reset-btn">重置</button>
|
||||
<button class="search-btn" @click="binaryStep">下一步</button>
|
||||
<button class="reset-btn" @click="resetBinary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="algo-stats">
|
||||
|
||||
+3
-3
@@ -22,9 +22,9 @@
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button @click="generateArray" class="control-btn">生成新数组</button>
|
||||
<button @click="startBubbleSort" class="control-btn">冒泡排序</button>
|
||||
<button @click="startQuickSort" class="control-btn">快速排序</button>
|
||||
<button class="control-btn" @click="generateArray">生成新数组</button>
|
||||
<button class="control-btn" @click="startBubbleSort">冒泡排序</button>
|
||||
<button class="control-btn" @click="startQuickSort">快速排序</button>
|
||||
</div>
|
||||
|
||||
<div class="algorithm-info">
|
||||
|
||||
@@ -54,8 +54,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong
|
||||
>存储遵循"金字塔"原则:越快的存储越贵、容量越小。CPU
|
||||
<strong>核心思想:</strong>存储遵循"金字塔"原则:越快的存储越贵、容量越小。CPU
|
||||
需要的数据放在最快的存储(寄存器、缓存),暂时不用的放在慢速大容量存储(磁盘、云端)。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-6
@@ -114,12 +114,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span class="legend-item"
|
||||
><span class="network-box" /> 网络位 ({{ cidr }}位)</span
|
||||
>
|
||||
<span class="legend-item"
|
||||
><span class="host-box" /> 主机位 ({{ 32 - cidr }}位)</span
|
||||
>
|
||||
<span class="legend-item"><span class="network-box" /> 网络位 ({{ cidr }}位)</span>
|
||||
<span class="legend-item"><span class="host-box" /> 主机位 ({{ 32 - cidr }}位)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-2
@@ -62,8 +62,7 @@
|
||||
v-for="(use, i) in currentProtocol.useCases"
|
||||
:key="i"
|
||||
class="use-tag"
|
||||
>{{ use }}</span
|
||||
>
|
||||
>{{ use }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-6
@@ -44,8 +44,7 @@
|
||||
:class="{
|
||||
sending: sendingBit === i && activeType === 'serial'
|
||||
}"
|
||||
>{{ bit }}</span
|
||||
>
|
||||
>{{ bit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="channels">
|
||||
@@ -57,8 +56,7 @@
|
||||
:key="i"
|
||||
class="flow-dot"
|
||||
:class="{ active: sendingBit !== null }"
|
||||
>●</span
|
||||
>
|
||||
>●</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="channel parallel">
|
||||
@@ -119,8 +117,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong
|
||||
>现代高速传输多采用串行方式。虽然并行"看起来"更快(一次传多位),但串行可以跑更高频率,抗干扰更强,实际速度反而更快。
|
||||
<strong>核心思想:</strong>现代高速传输多采用串行方式。虽然并行"看起来"更快(一次传多位),但串行可以跑更高频率,抗干扰更强,实际速度反而更快。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@
|
||||
<div class="comparison-side tcp-side">
|
||||
<div class="side-header">TCP</div>
|
||||
<div class="side-animation">
|
||||
<div class="packet" v-for="i in 3" :key="'tcp-' + i">
|
||||
<div v-for="i in 3" :key="'tcp-' + i" class="packet">
|
||||
📦 {{ i }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@
|
||||
<div class="comparison-side udp-side">
|
||||
<div class="side-header">UDP</div>
|
||||
<div class="side-animation">
|
||||
<div class="packet fast" v-for="i in 5" :key="'udp-' + i">
|
||||
<div v-for="i in 5" :key="'udp-' + i" class="packet fast">
|
||||
⚡ {{ i }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+11
-28
@@ -52,8 +52,7 @@
|
||||
v-for="t in selectedQuadrant.traits"
|
||||
:key="t"
|
||||
class="trait-tag"
|
||||
>{{ t }}</span
|
||||
>
|
||||
>{{ t }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,18 +110,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="convert-summary">
|
||||
<span v-if="activeLang === 'JavaScript'" class="summary-tag weak"
|
||||
>弱类型:隐式转换,结果常出人意料</span
|
||||
>
|
||||
<span v-else-if="activeLang === 'Python'" class="summary-tag strong"
|
||||
>强类型:拒绝隐式转换,必须显式指定</span
|
||||
>
|
||||
<span v-else-if="activeLang === 'Java'" class="summary-tag strong"
|
||||
>强类型:字符串拼接是特例,其余严格</span
|
||||
>
|
||||
<span v-else class="summary-tag strong"
|
||||
>强类型:类型不匹配就报错,零容忍</span
|
||||
>
|
||||
<span v-if="activeLang === 'JavaScript'" class="summary-tag weak">弱类型:隐式转换,结果常出人意料</span>
|
||||
<span v-else-if="activeLang === 'Python'" class="summary-tag strong">强类型:拒绝隐式转换,必须显式指定</span>
|
||||
<span v-else-if="activeLang === 'Java'" class="summary-tag strong">强类型:字符串拼接是特例,其余严格</span>
|
||||
<span v-else class="summary-tag strong">强类型:类型不匹配就报错,零容忍</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,7 +137,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="infer-benefit">
|
||||
<span class="benefit-item" v-for="b in inferBenefits" :key="b">{{
|
||||
<span v-for="b in inferBenefits" :key="b" class="benefit-item">{{
|
||||
b
|
||||
}}</span>
|
||||
</div>
|
||||
@@ -155,19 +146,11 @@
|
||||
|
||||
<div class="info-box">
|
||||
<strong>核心思想:</strong>
|
||||
<span v-if="activeTab === 'quadrant'"
|
||||
>类型系统在两个维度上做选择——何时检查(静态/动态)和是否允许隐式转换(强/弱)。没有最好的组合,只有最适合的场景。</span
|
||||
>
|
||||
<span v-else-if="activeTab === 'check'"
|
||||
>静态类型在编译时就能发现错误,动态类型要到运行时才知道——越早发现
|
||||
bug,修复成本越低。</span
|
||||
>
|
||||
<span v-else-if="activeTab === 'convert'"
|
||||
>弱类型语言会"猜"你的意思做隐式转换(常出错),强类型语言要求你明确表达意图(更安全)。</span
|
||||
>
|
||||
<span v-else
|
||||
>类型推断让你两全其美:代码像动态语言一样简洁,编译器像静态语言一样严格检查。</span
|
||||
>
|
||||
<span v-if="activeTab === 'quadrant'">类型系统在两个维度上做选择——何时检查(静态/动态)和是否允许隐式转换(强/弱)。没有最好的组合,只有最适合的场景。</span>
|
||||
<span v-else-if="activeTab === 'check'">静态类型在编译时就能发现错误,动态类型要到运行时才知道——越早发现
|
||||
bug,修复成本越低。</span>
|
||||
<span v-else-if="activeTab === 'convert'">弱类型语言会"猜"你的意思做隐式转换(常出错),强类型语言要求你明确表达意图(更安全)。</span>
|
||||
<span v-else>类型推断让你两全其美:代码像动态语言一样简洁,编译器像静态语言一样严格检查。</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user