318 lines
6.7 KiB
Vue
318 lines
6.7 KiB
Vue
<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>
|