feat(docs): add interactive demos and complete content for development tools

- Add Vue components for interactive demos (SSH auth, regex, env vars, ports)
- Complete markdown content for SSH, regex, environment variables, and ports
- Remove placeholder "待实现" sections and replace with detailed guides
- Add visual explanations for key concepts like ports and localhost
- Include practical examples and troubleshooting tips
- Add component for showing evolution from transistors to CPU
- Improve documentation structure and navigation
- Add security best practices for API keys and environment variables
This commit is contained in:
sanbuphy
2026-02-21 10:04:47 +08:00
parent 399913d3ff
commit 6098908eee
52 changed files with 17782 additions and 2725 deletions
@@ -2,308 +2,119 @@
<div class="logic-gate-demo">
<div class="demo-header">
<span class="title">逻辑门用开关做运算</span>
<span class="subtitle">晶体管组合成基本运算单元</span>
<span class="subtitle">切换输入 A / B同屏观察四种门的输出</span>
</div>
<div class="demo-content">
<div class="gate-tabs">
<button
v-for="gate in gates"
:key="gate.name"
:class="['tab-btn', { active: activeGate === gate.name }]"
@click="activeGate = gate.name"
>
{{ gate.name }}
<div class="control-panel">
<span class="panel-hint">点按钮切换 0 / 1右侧四个门同步更新</span>
<div class="input-item">
<span class="input-label">输入 A</span>
<button class="input-btn" :class="{ on: inputA }" @click="inputA = !inputA">
{{ inputA ? '1' : '0' }}
</button>
</div>
<div class="input-item">
<span class="input-label">输入 B</span>
<button class="input-btn" :class="{ on: inputB }" @click="inputB = !inputB">
{{ inputB ? '1' : '0' }}
</button>
</div>
<span class="current-state">当前A={{ inputA ? 1 : 0 }}B={{ inputB ? 1 : 0 }}</span>
</div>
<div class="gate-display">
<div class="gate-visual">
<div class="inputs">
<div class="input-item">
<span>A:</span>
<button
class="input-btn"
:class="{ on: inputA }"
@click="inputA = !inputA"
>
{{ inputA ? '1' : '0' }}
</button>
</div>
<div class="input-item">
<span>B:</span>
<button
class="input-btn"
:class="{ on: inputB }"
@click="inputB = !inputB"
>
{{ inputB ? '1' : '0' }}
</button>
</div>
</div>
<div class="gate-symbol">
<svg
viewBox="0 0 120 80"
class="gate-svg"
>
<template v-if="activeGate === 'AND'">
<path
d="M20,20 L20,60 L60,60 Q90,60 90,40 Q90,20 60,20 Z"
fill="none"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="90"
y1="40"
x2="110"
y2="40"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="30"
x2="20"
y2="30"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="50"
x2="20"
y2="50"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
</template>
<template v-else-if="activeGate === 'OR'">
<path
d="M20,20 Q40,40 20,60 Q60,60 90,40 Q60,20 20,20"
fill="none"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="90"
y1="40"
x2="110"
y2="40"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="30"
x2="25"
y2="30"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="50"
x2="25"
y2="50"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
</template>
<template v-else-if="activeGate === 'NOT'">
<polygon
points="20,20 80,40 20,60"
fill="none"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<circle
cx="85"
cy="40"
r="5"
fill="none"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="90"
y1="40"
x2="110"
y2="40"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="40"
x2="20"
y2="40"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
</template>
<template v-else-if="activeGate === 'XOR'">
<path
d="M25,20 Q45,40 25,60 Q65,60 95,40 Q65,20 25,20"
fill="none"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<path
d="M15,20 Q35,40 15,60"
fill="none"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="95"
y1="40"
x2="115"
y2="40"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="30"
x2="30"
y2="30"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
<line
x1="0"
y1="50"
x2="30"
y2="50"
stroke="var(--vp-c-brand)"
stroke-width="2"
/>
</template>
</svg>
<div class="gate-name">
{{ activeGate }}
</div>
</div>
<div class="output">
<span class="output-label">输出:</span>
<span
class="output-value"
:class="{ on: output }"
>{{ output ? '1' : '0' }}</span>
</div>
<div class="gate-grid">
<div v-for="gate in gates" :key="gate.name" class="gate-card">
<div class="gate-top">
<span class="gate-name">{{ gate.name }}</span>
<span class="gate-formula">{{ gate.formula }}</span>
</div>
<div class="truth-table-mini">
<div class="table-title">
{{ activeGate }} 真值表
</div>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>输出</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in currentTruthTable"
:key="row.a + '-' + row.b"
:class="{ highlight: row.a === (inputA ? 1 : 0) && (activeGate === 'NOT' || row.b === (inputB ? 1 : 0)) }"
>
<td>{{ row.a }}</td>
<td>{{ row.b }}</td>
<td>{{ row.out }}</td>
</tr>
</tbody>
</table>
<div class="gate-analogy">{{ gate.analogy }}</div>
<div class="gate-output-row">
<span class="output-label">输出</span>
<span
class="output-value"
:class="{ on: gateOutput(gate.name, inputA, inputB) }"
>
{{ gateOutput(gate.name, inputA, inputB) }}
</span>
<span class="output-hint">{{ gateOutput(gate.name, inputA, inputB) ? '(真 / 导通)' : '(假 / 断开)' }}</span>
</div>
</div>
</div>
<div class="gate-explanation">
<div class="exp-title">
{{ currentGate.expTitle }}
</div>
<div class="exp-content">
{{ currentGate.expContent }}
</div>
</div>
<div class="truth-section">
<div class="table-title">四种门真值表对照高亮行 = 当前输入</div>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>AND</th>
<th>OR</th>
<th>NOT(A)</th>
<th>XOR</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in truthRows"
:key="`${row.a}-${row.b}`"
:class="{
highlight:
row.a === (inputA ? 1 : 0) && row.b === (inputB ? 1 : 0)
}"
>
<td>{{ row.a }}</td>
<td>{{ row.b }}</td>
<td>{{ gateOutput('AND', !!row.a, !!row.b) }}</td>
<td>{{ gateOutput('OR', !!row.a, !!row.b) }}</td>
<td>{{ gateOutput('NOT', !!row.a, !!row.b) }}</td>
<td>{{ gateOutput('XOR', !!row.a, !!row.b) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="info-box">
<strong>核心思想</strong>逻辑门用晶体管的"开关"组合实现基本运算AND门像"串联开关"(两个都开才通)OR门像"并联开关"(任一个开就通)
<strong>核心思想</strong>逻辑门用晶体管的"开关"组合实现基本运算AND 像串联OR 像并联NOT 取反XOR 判异所有复杂计算都由这四种基础操作构建而来
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref } from 'vue'
const activeGate = ref('AND')
const inputA = ref(false)
const inputB = ref(false)
const gates = [
{
name: 'AND',
expTitle: 'AND门:两个都为1才输出1',
expContent: '就像串联的两个开关,必须两个都按下灯才会亮。用于"同时满足多个条件"的场景。'
},
{
name: 'OR',
expTitle: 'OR门:任一个为1就输出1',
expContent: '就像并联的两个开关,按任意一个灯就会亮。用于"满足任一条件"的场景。'
},
{
name: 'NOT',
expTitle: 'NOT门:取反,0变11变0',
expContent: '就像一个反相器,输入开就输出关,输入关就输出开。用于"否定"的场景。'
},
{
name: 'XOR',
expTitle: 'XOR门:相同为0,不同为1',
expContent: '就像"异或"判断,两个输入不同才输出1。用于比较两个值是否不同。'
}
{ name: 'AND', formula: 'A && B', analogy: '串联:都为 1 才输出 1' },
{ name: 'OR', formula: 'A || B', analogy: '并联:任一为 1 就输出 1' },
{ name: 'NOT', formula: '!A', analogy: '取反:0→11→0' },
{ name: 'XOR', formula: 'A ⊕ B', analogy: '判异:不同为 1,相同为 0' }
]
const currentGate = computed(() => gates.find(g => g.name === activeGate.value))
const truthRows = [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 0 },
{ a: 1, b: 1 }
]
const output = computed(() => {
const a = inputA.value
const b = inputB.value
switch (activeGate.value) {
case 'AND': return a && b ? 1 : 0
case 'OR': return a || b ? 1 : 0
case 'NOT': return a ? 0 : 1
case 'XOR': return a !== b ? 1 : 0
default: return 0
const gateOutput = (name, a, b) => {
switch (name) {
case 'AND':
return a && b ? 1 : 0
case 'OR':
return a || b ? 1 : 0
case 'NOT':
return a ? 0 : 1
case 'XOR':
return a !== b ? 1 : 0
default:
return 0
}
})
const currentTruthTable = computed(() => {
if (activeGate.value === 'NOT') {
return [
{ a: 0, b: '-', out: 1 },
{ a: 1, b: '-', out: 0 }
]
}
const table = []
for (let a = 0; a <= 1; a++) {
for (let b = 0; b <= 1; b++) {
let out = 0
switch (activeGate.value) {
case 'AND': out = a && b ? 1 : 0; break
case 'OR': out = a || b ? 1 : 0; break
case 'XOR': out = a !== b ? 1 : 0; break
}
table.push({ a, b, out })
}
}
return table
})
}
</script>
<style scoped>
@@ -320,65 +131,66 @@ const currentTruthTable = computed(() => {
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.gate-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.tab-btn {
padding: 0.4rem 0.8rem;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.tab-btn.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.gate-display {
display: flex;
gap: 1.5rem;
align-items: flex-start;
flex-wrap: wrap;
}
.gate-visual {
display: flex;
align-items: center;
gap: 1rem;
.demo-header .title {
font-weight: bold;
font-size: 1rem;
}
.inputs {
.demo-header .subtitle {
color: var(--vp-c-text-2);
font-size: 0.82rem;
margin-left: 0.5rem;
}
.control-panel {
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: center;
gap: 0.6rem;
margin-bottom: 0.75rem;
flex-wrap: wrap;
padding: 0.5rem 0.65rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
}
.panel-hint {
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.current-state {
font-size: 0.8rem;
color: var(--vp-c-text-2);
margin-left: auto;
font-variant-numeric: tabular-nums;
}
.input-item {
display: flex;
display: inline-flex;
align-items: center;
gap: 0.5rem;
gap: 0.4rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
padding: 0.2rem 0.5rem;
font-size: 0.85rem;
}
.input-label {
font-size: 0.82rem;
color: var(--vp-c-text-2);
}
.input-btn {
width: 36px;
height: 36px;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 4px;
width: 28px;
height: 28px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-alt);
border-radius: 50%;
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
@@ -390,44 +202,76 @@ const currentTruthTable = computed(() => {
border-color: var(--vp-c-brand);
}
.gate-symbol {
width: 120px;
text-align: center;
.gate-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.gate-svg {
width: 100%;
height: 60px;
.gate-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 0.55rem;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.gate-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.gate-name {
font-weight: bold;
font-size: 0.9rem;
margin-top: 0.25rem;
color: var(--vp-c-brand);
font-size: 0.85rem;
color: var(--vp-c-brand-1);
}
.output {
.gate-formula {
font-family: monospace;
font-size: 0.72rem;
color: var(--vp-c-text-3);
}
.gate-analogy {
font-size: 0.72rem;
color: var(--vp-c-text-2);
line-height: 1.3;
}
.gate-output-row {
display: flex;
align-items: center;
gap: 0.5rem;
gap: 0.35rem;
margin-top: 0.1rem;
}
.output-label {
font-size: 0.85rem;
font-size: 0.7rem;
color: var(--vp-c-text-3);
}
.output-hint {
font-size: 0.68rem;
color: var(--vp-c-text-3);
}
.output-value {
width: 36px;
height: 36px;
display: flex;
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: var(--vp-c-bg-alt);
font-weight: bold;
font-size: 1.1rem;
font-size: 0.85rem;
flex-shrink: 0;
}
.output-value.on {
@@ -436,56 +280,42 @@ const currentTruthTable = computed(() => {
border-color: var(--vp-c-success);
}
.truth-table-mini {
flex: 1;
min-width: 150px;
.truth-section {
margin-bottom: 0;
}
.table-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
font-size: 0.82rem;
margin-bottom: 0.4rem;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 0.8rem;
}
th, td {
th,
td {
border: 1px solid var(--vp-c-divider);
padding: 0.3rem 0.5rem;
padding: 0;
height: 2rem;
vertical-align: middle;
text-align: center;
font-variant-numeric: tabular-nums;
}
th {
background: var(--vp-c-bg-alt);
font-weight: 600;
}
tr.highlight {
background: var(--vp-c-brand-soft);
}
.gate-explanation {
width: 100%;
margin-top: 1rem;
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border-radius: 6px;
}
.exp-title {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.exp-content {
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
@@ -497,4 +327,14 @@ tr.highlight {
gap: 0.25rem;
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
}
@media (max-width: 640px) {
.gate-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>