feat: 更新附录交互组件和文档

This commit is contained in:
sanbuphy
2026-02-24 00:18:09 +08:00
parent d45df3cda5
commit 94f9db0834
88 changed files with 11797 additions and 7634 deletions
@@ -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>
@@ -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>
@@ -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 = (AB) (xor1Cin) =
<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] }
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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 其实被切碎分别放在了第 3814
文件系统帮你做好了翻译你只需双击它
</span>
<span v-if="activeFile === 'vacation'">
💡 旅游.png 放在了第 56
</span>
<span v-if="activeFile === 'doc'">
💡 总结.docx 被分散存放在 10111822
如果没有文件系统你得自己背下这些数字才能打开文件
</span>
</div>
<div class="explanation-box default" v-else>
试着点击左侧的文件看看它们在硬盘里到底长什么样
<div class="explain">
<strong>💡 原理</strong>文件系统把文件切成碎片存在硬盘各处如宠物.jpg存在第3711然后用"账本"记录位置你看到的整齐文件夹只是账本上的记录
</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>
@@ -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 = (AB) (xor1Cin) = ({{ 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第二级半加器把结果加上 CinOR 门合并两路进位信号
</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>
@@ -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">
@@ -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>
@@ -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>
@@ -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简单通用再学 JavaScriptWeb
必备最后选一门静态语言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简单通用再学 JavaScriptWeb
必备最后选一门静态语言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>
@@ -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>
@@ -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 // 100ms10ms
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>
@@ -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>
@@ -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>
@@ -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 -->
@@ -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">
@@ -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>
@@ -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>
@@ -62,8 +62,7 @@
v-for="(use, i) in currentProtocol.useCases"
:key="i"
class="use-tag"
>{{ use }}</span
>
>{{ use }}</span>
</div>
</div>
</div>
@@ -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>
@@ -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>
@@ -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>