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
@@ -1,376 +1,259 @@
<template>
<div class="process-demo">
<div class="controls-section">
<button
class="action-btn"
:class="{ active: isRunning }"
@click="toggleSimulation"
>
{{ isRunning ? '⏸ 暂停时间片轮转' : '▶️ 启动 CPU' }}
</button>
<div class="speed-control">
<label>时间流速:</label>
<button :class="{ active: speed === 'slow' }" @click="setSpeed('slow')">
极慢动作
</button>
<button :class="{ active: speed === 'fast' }" @click="setSpeed('fast')">
真实速度
</button>
<div class="demo">
<div class="title"> CPU 在疯狂切换你感觉不出来</div>
<div class="cpu-core">
<div class="cpu-label">CPU</div>
<div class="current-task" :class="{ switching: isSwitching }">
<span class="task-icon">{{ currentTask.icon }}</span>
<span class="task-name">{{ currentTask.name }}</span>
</div>
<div class="time-slice">时间片: {{ timeLeft }}ms</div>
</div>
<div class="cpu-container">
<div class="cpu-core" :class="{ active: isRunning }">
<div class="cpu-title">单核 CPU</div>
<div class="current-task">
<span v-if="activeProcess" class="task-badge">
正在处理: {{ activeProcess.icon }} {{ activeProcess.name }}
</span>
<span v-else class="task-badge idle"> 空闲中... </span>
</div>
</div>
<!-- 连接线动画 -->
<div class="connector">
<div
class="data-flow"
:class="[`flow-${activeProcessId}`, { running: isRunning }]"
></div>
</div>
</div>
<div class="processes-grid">
<div class="process-queue">
<div
v-for="p in processes"
:key="p.id"
class="process-card"
:class="{ active: p.id === activeProcessId }"
v-for="(proc, idx) in processes"
:key="proc.id"
class="process"
:class="{
active: idx === currentIdx,
waiting: idx !== currentIdx,
done: proc.progress >= 100
}"
:style="{ '--progress': proc.progress + '%' }"
>
<div class="p-header">
<div class="p-title">
<span class="icon">{{ p.icon }}</span>
<span class="name">{{ p.name }}</span>
<span class="p-icon">{{ proc.icon }}</span>
<div class="p-info">
<span class="p-name">{{ proc.name }}</span>
<div class="p-bar">
<div class="p-fill"></div>
</div>
<span
class="status-badge"
:class="p.id === activeProcessId ? 'running' : 'waiting'"
>
{{ p.id === activeProcessId ? '独占 CPU' : '排队等待' }}
</span>
</div>
<div class="p-progress">
<div class="progress-track">
<div
class="progress-fill"
:style="{ width: p.progress + '%' }"
></div>
</div>
<div class="progress-text">{{ Math.floor(p.progress) }}% 完成</div>
</div>
<span class="p-status">{{ idx === currentIdx ? '运行中' : (proc.progress >= 100 ? '完成' : '等待') }}</span>
</div>
</div>
<div
class="explanation-box"
:class="{ show: isRunning && speed === 'fast' }"
>
💡
**关键启示**当切换速度足够快时肉眼已经无法分辨谁在等待这也就是为什么只有一个
CPU 核心的电脑依然能让你一边听歌一边打字
<div class="explain">
<strong>💡 原理</strong>CPU {{ sliceTime }}ms 切换一次进程因为太快了你感觉是"同时运行"实际上每个进程都在断断续续地执行
</div>
</div>
</template>
<script setup>
import { ref, computed, onUnmounted } from 'vue'
const isRunning = ref(false)
const activeProcessId = ref(null)
const speed = ref('slow')
let interval = null
import { ref, computed, onMounted, onUnmounted } from 'vue'
const processes = ref([
{ id: 1, name: '微信接收', icon: '💬', progress: 0 },
{ id: 2, name: '音乐播放', icon: '🎵', progress: 0 },
{ id: 3, name: '游戏渲染', icon: '🎮', progress: 0 }
{ id: 1, name: '微信', icon: '💬', progress: 0 },
{ id: 2, name: '音乐', icon: '🎵', progress: 0 },
{ id: 3, name: '浏览器', icon: '🌐', progress: 0 }
])
const activeProcess = computed(() =>
processes.value.find((p) => p.id === activeProcessId.value)
)
const currentIdx = ref(0)
const timeLeft = ref(0)
const isSwitching = ref(false)
const sliceTime = 100 // 每个时间片100ms(演示用,实际是10ms左右)
const setSpeed = (s) => {
speed.value = s
if (isRunning.value) {
clearInterval(interval)
startLoop()
let timer = null
let switchTimer = null
const switchTask = () => {
isSwitching.value = true
setTimeout(() => {
currentIdx.value = (currentIdx.value + 1) % processes.value.length
timeLeft.value = sliceTime
isSwitching.value = false
}, 200)
}
const tick = () => {
const current = processes.value[currentIdx.value]
// 当前进程执行
if (current.progress < 100) {
current.progress = Math.min(100, current.progress + 5)
}
// 时间片倒计时
timeLeft.value -= 10
// 时间片用完,切换
if (timeLeft.value <= 0) {
switchTask()
}
// 检查是否全部完成
if (processes.value.every(p => p.progress >= 100)) {
// 重置演示
setTimeout(() => {
processes.value.forEach(p => p.progress = 0)
currentIdx.value = 0
timeLeft.value = sliceTime
}, 2000)
}
}
const startLoop = () => {
const switchTime = speed.value === 'slow' ? 1200 : 80 // 慢动作 1.2s,快动作极快
if (!activeProcessId.value) {
activeProcessId.value = 1
}
interval = setInterval(() => {
// 增加当前进度
const curr = processes.value.find((p) => p.id === activeProcessId.value)
if (curr) {
curr.progress += speed.value === 'slow' ? 15 : 4
if (curr.progress >= 100) curr.progress = 0
}
// 切换下一个
let nextId = activeProcessId.value + 1
if (nextId > 3) nextId = 1
activeProcessId.value = nextId
}, switchTime)
}
const toggleSimulation = () => {
if (isRunning.value) {
clearInterval(interval)
isRunning.value = false
activeProcessId.value = null
} else {
isRunning.value = true
startLoop()
}
}
onMounted(() => {
timeLeft.value = sliceTime
timer = setInterval(tick, 10) // 每10ms更新一次
})
onUnmounted(() => {
if (interval) clearInterval(interval)
clearInterval(timer)
clearTimeout(switchTimer)
})
const currentTask = computed(() => processes.value[currentIdx.value])
</script>
<style scoped>
.process-demo {
background: var(--vp-c-bg-soft);
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.controls-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.action-btn {
background: var(--vp-c-brand-1);
color: white;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 16px;
margin: 1rem 0;
}
.title {
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 160px;
}
.action-btn.active {
background: var(--vp-c-danger-1);
}
.action-btn:hover {
filter: brightness(1.1);
transform: translateY(-1px);
}
.speed-control {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
color: var(--vp-c-text-2);
}
.speed-control button {
background: transparent;
border: 1px solid var(--vp-c-divider);
padding: 0.3rem 0.8rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.speed-control button.active {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
font-weight: bold;
}
.cpu-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 2rem;
font-size: 14px;
margin-bottom: 12px;
text-align: center;
}
.cpu-core {
width: 240px;
height: 90px;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
background: linear-gradient(135deg, #667eea22, #764ba222);
border: 2px solid #667eea;
border-radius: 8px;
padding: 12px;
text-align: center;
margin-bottom: 12px;
position: relative;
}
.cpu-core.active {
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 20px var(--vp-c-brand-soft);
}
.cpu-title {
font-weight: 800;
font-size: 1.1rem;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
letter-spacing: 2px;
.cpu-label {
font-size: 10px;
color: var(--vp-c-text-3);
margin-bottom: 4px;
}
.current-task {
height: 28px;
display: flex;
align-items: center;
}
.task-badge {
background: var(--vp-c-brand-1);
color: white;
padding: 0.2rem 0.8rem;
border-radius: 12px;
font-size: 0.85rem;
justify-content: center;
gap: 8px;
font-size: 18px;
font-weight: 600;
}
.task-badge.idle {
background: var(--vp-c-text-3);
transition: all 0.2s;
}
/* 连接线动画占位,简化效果,用发亮的虚线替代 */
.connector {
width: 2px;
height: 30px;
background: var(--vp-c-divider);
margin-top: 5px;
position: relative;
.current-task.switching {
opacity: 0.3;
transform: scale(0.9);
}
.processes-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
.task-icon {
font-size: 24px;
}
@media (max-width: 640px) {
.processes-grid {
grid-template-columns: 1fr;
}
}
.process-card {
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 1rem;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.process-card.active {
border-color: var(--vp-c-brand-1);
transform: translateY(-2px);
box-shadow: 0 6px 16px var(--vp-c-brand-soft);
}
.process-card.active::before {
content: '';
.time-slice {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--vp-c-brand-1);
}
.p-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.p-title {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 600;
}
.status-badge {
font-size: 0.75rem;
padding: 0.1rem 0.5rem;
top: 8px;
right: 12px;
font-size: 10px;
color: var(--vp-c-text-3);
background: var(--vp-c-bg);
padding: 2px 6px;
border-radius: 4px;
font-weight: bold;
}
.status-badge.waiting {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
}
.status-badge.running {
background: rgba(16, 185, 129, 0.15);
color: var(--vp-c-success-1);
}
.p-progress {
.process-queue {
display: flex;
flex-direction: column;
gap: 0.5rem;
gap: 8px;
margin-bottom: 12px;
}
.progress-track {
width: 100%;
height: 8px;
.process {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
transition: all 0.3s;
}
.process.active {
border-color: #667eea;
background: #667eea11;
box-shadow: 0 0 10px #667eea33;
}
.process.done {
opacity: 0.6;
}
.process.done .p-fill {
background: #10b981;
}
.p-icon {
font-size: 20px;
width: 24px;
text-align: center;
}
.p-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.p-name {
font-size: 12px;
font-weight: 600;
}
.p-bar {
height: 4px;
background: var(--vp-c-bg-soft);
border-radius: 4px;
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
.p-fill {
height: 100%;
background: var(--vp-c-brand-1);
width: var(--progress);
background: #667eea;
border-radius: 2px;
transition: width 0.1s linear;
}
.process-card.active .progress-fill {
background: var(--vp-c-success-1);
.p-status {
font-size: 10px;
color: var(--vp-c-text-3);
padding: 2px 6px;
background: var(--vp-c-bg-soft);
border-radius: 4px;
}
.progress-text {
font-size: 0.75rem;
.process.active .p-status {
color: #667eea;
background: #667eea22;
}
.explain {
font-size: 12px;
color: var(--vp-c-text-2);
text-align: right;
font-variant-numeric: tabular-nums;
line-height: 1.5;
padding: 10px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.explanation-box {
margin-top: 1.5rem;
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
opacity: 0;
transform: translateY(10px);
transition: all 0.5s ease;
}
.explanation-box.show {
opacity: 1;
transform: translateY(0);
}
.explain strong { color: var(--vp-c-text-1); }
</style>