2026-02-18 15:52:55 +08:00
|
|
|
|
<template>
|
2026-02-24 00:18:09 +08:00
|
|
|
|
<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>
|
2026-02-23 01:40:56 +08:00
|
|
|
|
</div>
|
2026-02-24 00:18:09 +08:00
|
|
|
|
<div class="time-slice">时间片: {{ timeLeft }}ms</div>
|
2026-02-23 01:40:56 +08:00
|
|
|
|
</div>
|
2026-02-18 15:52:55 +08:00
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
<div class="process-queue">
|
2026-02-23 01:50:43 +08:00
|
|
|
|
<div
|
2026-02-24 00:18:09 +08:00
|
|
|
|
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 + '%' }"
|
2026-02-18 17:38:10 +08:00
|
|
|
|
>
|
2026-02-24 00:18:09 +08:00
|
|
|
|
<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>
|
2026-02-18 15:52:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-24 00:18:09 +08:00
|
|
|
|
<span class="p-status">{{ idx === currentIdx ? '运行中' : (proc.progress >= 100 ? '完成' : '等待') }}</span>
|
2026-02-18 15:52:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
<div class="explain">
|
|
|
|
|
|
<strong>💡 原理:</strong>CPU 每 {{ sliceTime }}ms 切换一次进程,因为太快了你感觉是"同时运行"。实际上每个进程都在断断续续地执行。
|
2026-02-18 15:52:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-02-24 00:18:09 +08:00
|
|
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
2026-02-18 15:52:55 +08:00
|
|
|
|
|
|
|
|
|
|
const processes = ref([
|
2026-02-24 00:18:09 +08:00
|
|
|
|
{ id: 1, name: '微信', icon: '💬', progress: 0 },
|
|
|
|
|
|
{ id: 2, name: '音乐', icon: '🎵', progress: 0 },
|
|
|
|
|
|
{ id: 3, name: '浏览器', icon: '🌐', progress: 0 }
|
2026-02-18 15:52:55 +08:00
|
|
|
|
])
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
const currentIdx = ref(0)
|
|
|
|
|
|
const timeLeft = ref(0)
|
|
|
|
|
|
const isSwitching = ref(false)
|
|
|
|
|
|
const sliceTime = 100 // 每个时间片100ms(演示用,实际是10ms左右)
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2026-02-23 01:40:56 +08:00
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 时间片倒计时
|
|
|
|
|
|
timeLeft.value -= 10
|
|
|
|
|
|
|
|
|
|
|
|
// 时间片用完,切换
|
|
|
|
|
|
if (timeLeft.value <= 0) {
|
|
|
|
|
|
switchTask()
|
2026-02-23 01:40:56 +08:00
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查是否全部完成
|
|
|
|
|
|
if (processes.value.every(p => p.progress >= 100)) {
|
|
|
|
|
|
// 重置演示
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
processes.value.forEach(p => p.progress = 0)
|
|
|
|
|
|
currentIdx.value = 0
|
|
|
|
|
|
timeLeft.value = sliceTime
|
|
|
|
|
|
}, 2000)
|
2026-02-23 01:40:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
timeLeft.value = sliceTime
|
|
|
|
|
|
timer = setInterval(tick, 10) // 每10ms更新一次
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-02-23 01:40:56 +08:00
|
|
|
|
onUnmounted(() => {
|
2026-02-26 04:35:28 +08:00
|
|
|
|
if (timer) clearInterval(timer)
|
|
|
|
|
|
if (switchTimer) clearTimeout(switchTimer)
|
2026-02-23 01:40:56 +08:00
|
|
|
|
})
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
const currentTask = computed(() => processes.value[currentIdx.value])
|
2026-02-18 15:52:55 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.demo {
|
2026-02-23 01:40:56 +08:00
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
|
|
|
|
|
border-radius: 8px;
|
2026-02-24 00:18:09 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin: 1rem 0;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.title {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
text-align: center;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-23 01:40:56 +08:00
|
|
|
|
.cpu-core {
|
2026-02-24 00:18:09 +08:00
|
|
|
|
background: linear-gradient(135deg, #667eea22, #764ba222);
|
|
|
|
|
|
border: 2px solid #667eea;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 12px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
.cpu-label {
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
color: var(--vp-c-text-3);
|
|
|
|
|
|
margin-bottom: 4px;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-23 01:40:56 +08:00
|
|
|
|
.current-task {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-02-24 00:18:09 +08:00
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
font-size: 18px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
font-weight: 600;
|
2026-02-24 00:18:09 +08:00
|
|
|
|
transition: all 0.2s;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
.current-task.switching {
|
|
|
|
|
|
opacity: 0.3;
|
|
|
|
|
|
transform: scale(0.9);
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.task-icon {
|
|
|
|
|
|
font-size: 24px;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.time-slice {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 8px;
|
|
|
|
|
|
right: 12px;
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
color: var(--vp-c-text-3);
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
|
border-radius: 4px;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.process-queue {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 12px;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.process {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
background: var(--vp-c-bg);
|
2026-02-23 01:40:56 +08:00
|
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-02-24 00:18:09 +08:00
|
|
|
|
border-radius: 6px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
transition: all 0.3s;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.process.active {
|
|
|
|
|
|
border-color: #667eea;
|
|
|
|
|
|
background: #667eea11;
|
|
|
|
|
|
box-shadow: 0 0 10px #667eea33;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.process.done {
|
|
|
|
|
|
opacity: 0.6;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.process.done .p-fill {
|
|
|
|
|
|
background: #10b981;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.p-icon {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
text-align: center;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.p-info {
|
|
|
|
|
|
flex: 1;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-02-24 00:18:09 +08:00
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.p-name {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.p-bar {
|
|
|
|
|
|
height: 4px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
background: var(--vp-c-bg-soft);
|
2026-02-24 00:18:09 +08:00
|
|
|
|
border-radius: 2px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
overflow: hidden;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
.p-fill {
|
2026-02-23 01:40:56 +08:00
|
|
|
|
height: 100%;
|
2026-02-24 00:18:09 +08:00
|
|
|
|
width: var(--progress);
|
|
|
|
|
|
background: #667eea;
|
|
|
|
|
|
border-radius: 2px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
transition: width 0.1s linear;
|
|
|
|
|
|
}
|
2026-02-18 15:52:55 +08:00
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.p-status {
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
color: var(--vp-c-text-3);
|
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
|
background: var(--vp-c-bg-soft);
|
|
|
|
|
|
border-radius: 4px;
|
2026-02-18 15:52:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 00:18:09 +08:00
|
|
|
|
.process.active .p-status {
|
|
|
|
|
|
color: #667eea;
|
|
|
|
|
|
background: #667eea22;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
.explain {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--vp-c-text-2);
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
|
border-radius: 6px;
|
2026-02-23 01:40:56 +08:00
|
|
|
|
}
|
2026-02-24 00:18:09 +08:00
|
|
|
|
|
|
|
|
|
|
.explain strong { color: var(--vp-c-text-1); }
|
2026-02-18 15:52:55 +08:00
|
|
|
|
</style>
|