2026-01-15 20:10:19 +08:00
|
|
|
<template>
|
2026-01-16 19:10:21 +08:00
|
|
|
<div class="url-to-browser-demo">
|
2026-01-18 10:24:35 +08:00
|
|
|
<div class="stage-tracker">
|
2026-01-16 19:10:21 +08:00
|
|
|
<button
|
|
|
|
|
v-for="(stage, index) in stages"
|
|
|
|
|
:key="index"
|
2026-01-18 10:24:35 +08:00
|
|
|
class="tracker-node"
|
2026-01-18 12:21:49 +08:00
|
|
|
:class="{
|
|
|
|
|
active: currentStage === index,
|
|
|
|
|
visited: currentStage > index
|
|
|
|
|
}"
|
2026-01-16 19:10:21 +08:00
|
|
|
@click="currentStage = index"
|
|
|
|
|
>
|
2026-01-18 10:24:35 +08:00
|
|
|
<div class="node-circle">
|
|
|
|
|
<span class="icon">{{ stage.icon }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="node-label">{{ stage.name }}</span>
|
2026-01-16 19:10:21 +08:00
|
|
|
</button>
|
2026-01-18 10:24:35 +08:00
|
|
|
<div class="tracker-line">
|
2026-01-18 12:21:49 +08:00
|
|
|
<div
|
|
|
|
|
class="line-fill"
|
|
|
|
|
:style="{ width: (currentStage / (stages.length - 1)) * 100 + '%' }"
|
2026-02-18 17:38:10 +08:00
|
|
|
/>
|
2026-01-18 10:24:35 +08:00
|
|
|
</div>
|
2026-01-15 20:10:19 +08:00
|
|
|
</div>
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
<div class="stage-display">
|
|
|
|
|
<div class="header">
|
|
|
|
|
<h2>{{ stages[currentStage].title }}</h2>
|
|
|
|
|
<p>{{ stages[currentStage].desc }}</p>
|
|
|
|
|
</div>
|
2026-01-18 12:21:49 +08:00
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
<div class="component-wrapper">
|
2026-02-18 17:38:10 +08:00
|
|
|
<transition
|
|
|
|
|
name="fade"
|
|
|
|
|
mode="out-in"
|
|
|
|
|
>
|
|
|
|
|
<component
|
|
|
|
|
:is="stages[currentStage].component"
|
|
|
|
|
:key="currentStage"
|
|
|
|
|
/>
|
2026-01-18 10:24:35 +08:00
|
|
|
</transition>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-18 17:38:10 +08:00
|
|
|
<div
|
|
|
|
|
v-if="currentStage < stages.length - 1"
|
|
|
|
|
class="action-footer"
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
class="next-btn"
|
|
|
|
|
@click="nextStage"
|
|
|
|
|
>
|
|
|
|
|
下一步 →
|
|
|
|
|
</button>
|
2026-01-18 10:24:35 +08:00
|
|
|
</div>
|
2026-01-15 20:10:19 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
|
2026-01-16 19:10:21 +08:00
|
|
|
const currentStage = ref(0)
|
2026-01-15 20:10:19 +08:00
|
|
|
|
2026-01-16 19:10:21 +08:00
|
|
|
const stages = [
|
2026-01-15 20:10:19 +08:00
|
|
|
{
|
2026-01-16 19:10:21 +08:00
|
|
|
name: 'URL',
|
2026-01-18 10:24:35 +08:00
|
|
|
title: '1. 填写购物单 (URL)',
|
|
|
|
|
desc: '你想买一个玩具。首先要在订单上写清楚:去哪家店 (域名)、买什么 (路径)、用什么快递 (协议)。',
|
|
|
|
|
icon: '📝',
|
2026-01-16 19:10:21 +08:00
|
|
|
component: 'UrlParserDemo'
|
2026-01-15 20:10:19 +08:00
|
|
|
},
|
|
|
|
|
{
|
2026-01-16 19:10:21 +08:00
|
|
|
name: 'DNS',
|
2026-01-18 10:24:35 +08:00
|
|
|
title: '2. 查找店铺地址 (DNS)',
|
|
|
|
|
desc: '快递员不知道 "玩具店" 在哪。他需要查地图 (DNS),把店名翻译成具体的 GPS 坐标 (IP 地址)。',
|
|
|
|
|
icon: '🧭',
|
2026-01-16 19:10:21 +08:00
|
|
|
component: 'DnsLookupDemo'
|
2026-01-15 20:10:19 +08:00
|
|
|
},
|
|
|
|
|
{
|
2026-01-16 19:10:21 +08:00
|
|
|
name: 'TCP',
|
2026-01-18 10:24:35 +08:00
|
|
|
title: '3. 建立通话 (TCP)',
|
|
|
|
|
desc: '找到店了!进店前先敲门确认:"有人吗?" "有!" "那我进来了!"。确保连接通畅,不会白跑一趟。',
|
|
|
|
|
icon: '📞',
|
2026-01-16 19:10:21 +08:00
|
|
|
component: 'TcpHandshakeDemo'
|
2026-01-15 20:10:19 +08:00
|
|
|
},
|
|
|
|
|
{
|
2026-01-16 19:10:21 +08:00
|
|
|
name: 'HTTP',
|
2026-01-18 10:24:35 +08:00
|
|
|
title: '4. 购买商品 (HTTP)',
|
|
|
|
|
desc: '进店后,你递交订单:"我要这个玩具"。店员去仓库找货,最后把装有玩具的包裹 (HTML) 递给你。',
|
|
|
|
|
icon: '📦',
|
2026-01-16 19:10:21 +08:00
|
|
|
component: 'HttpExchangeDemo'
|
2026-01-15 20:10:19 +08:00
|
|
|
},
|
|
|
|
|
{
|
2026-01-16 19:10:21 +08:00
|
|
|
name: 'Render',
|
2026-01-18 10:24:35 +08:00
|
|
|
title: '5. 拆盒组装 (渲染)',
|
|
|
|
|
desc: '回到家,拆开包裹。照着说明书 (HTML),把积木 (DOM) 搭起来,涂上颜色 (CSS),玩具就变好看了!',
|
|
|
|
|
icon: '🧩',
|
2026-01-16 19:10:21 +08:00
|
|
|
component: 'BrowserRenderingDemo'
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
]
|
2026-01-18 10:24:35 +08:00
|
|
|
|
|
|
|
|
const nextStage = () => {
|
|
|
|
|
if (currentStage.value < stages.length - 1) {
|
|
|
|
|
currentStage.value++
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-15 20:10:19 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-01-16 19:10:21 +08:00
|
|
|
.url-to-browser-demo {
|
2026-01-15 20:10:19 +08:00
|
|
|
border: 1px solid var(--vp-c-divider);
|
2026-01-18 10:24:35 +08:00
|
|
|
border-radius: 12px;
|
2026-01-16 19:10:21 +08:00
|
|
|
background-color: var(--vp-c-bg-soft);
|
2026-01-18 10:24:35 +08:00
|
|
|
overflow: hidden;
|
2026-01-16 19:10:21 +08:00
|
|
|
margin: 2rem 0;
|
2026-01-18 12:21:49 +08:00
|
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.05);
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.stage-tracker {
|
2026-01-15 20:10:19 +08:00
|
|
|
display: flex;
|
2026-01-16 19:10:21 +08:00
|
|
|
justify-content: space-between;
|
2026-01-18 10:24:35 +08:00
|
|
|
padding: 2rem 2rem 1rem;
|
2026-01-15 20:10:19 +08:00
|
|
|
position: relative;
|
2026-01-18 10:24:35 +08:00
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
border-bottom: 1px solid var(--vp-c-divider);
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.tracker-line {
|
2026-01-15 20:10:19 +08:00
|
|
|
position: absolute;
|
2026-01-18 10:24:35 +08:00
|
|
|
top: 3.2rem; /* Adjusted for padding */
|
|
|
|
|
left: 3.5rem;
|
|
|
|
|
right: 3.5rem;
|
2026-01-16 19:10:21 +08:00
|
|
|
height: 2px;
|
2026-01-15 20:10:19 +08:00
|
|
|
background: var(--vp-c-divider);
|
2026-01-16 19:10:21 +08:00
|
|
|
z-index: 0;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.line-fill {
|
|
|
|
|
height: 100%;
|
|
|
|
|
background: var(--vp-c-brand);
|
|
|
|
|
transition: width 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracker-node {
|
2026-01-16 19:10:21 +08:00
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
2026-01-18 10:24:35 +08:00
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
2026-01-15 20:10:19 +08:00
|
|
|
display: flex;
|
2026-01-18 10:24:35 +08:00
|
|
|
flex-direction: column;
|
2026-01-15 20:10:19 +08:00
|
|
|
align-items: center;
|
2026-01-16 19:10:21 +08:00
|
|
|
gap: 0.5rem;
|
|
|
|
|
cursor: pointer;
|
2026-01-18 10:24:35 +08:00
|
|
|
padding: 0;
|
|
|
|
|
width: 60px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.node-circle {
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: var(--vp-c-bg);
|
|
|
|
|
border: 2px solid var(--vp-c-divider);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
font-size: 1.2rem;
|
|
|
|
|
transition: all 0.3s;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.tracker-node.visited .node-circle {
|
2026-01-15 20:10:19 +08:00
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
background: var(--vp-c-brand);
|
|
|
|
|
color: white;
|
2026-01-18 10:24:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracker-node.active .node-circle {
|
|
|
|
|
border-color: var(--vp-c-brand);
|
|
|
|
|
box-shadow: 0 0 0 4px var(--vp-c-brand-dimm);
|
2026-01-16 19:10:21 +08:00
|
|
|
transform: scale(1.1);
|
2026-01-18 10:24:35 +08:00
|
|
|
background: var(--vp-c-bg);
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.node-label {
|
|
|
|
|
font-size: 0.75rem;
|
2026-01-15 20:10:19 +08:00
|
|
|
font-weight: bold;
|
2026-01-18 10:24:35 +08:00
|
|
|
color: var(--vp-c-text-2);
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.tracker-node.active .node-label {
|
|
|
|
|
color: var(--vp-c-brand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stage-display {
|
2026-01-16 19:10:21 +08:00
|
|
|
padding: 2rem;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.header {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 2rem;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.header h2 {
|
|
|
|
|
border: none;
|
|
|
|
|
margin: 0 0 0.5rem 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
font-size: 1.5rem;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.header p {
|
|
|
|
|
margin: 0;
|
2026-01-15 20:10:19 +08:00
|
|
|
color: var(--vp-c-text-2);
|
2026-01-18 10:24:35 +08:00
|
|
|
max-width: 600px;
|
2026-01-16 19:10:21 +08:00
|
|
|
margin: 0 auto;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 10:24:35 +08:00
|
|
|
.component-wrapper {
|
|
|
|
|
background: var(--vp-c-bg);
|
2026-02-14 20:23:34 +08:00
|
|
|
border-radius: 6px;
|
|
|
|
|
/* padding: 0.75rem; */
|
2026-01-18 10:24:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-footer {
|
|
|
|
|
margin-top: 2rem;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-btn {
|
|
|
|
|
padding: 0.8rem 2rem;
|
|
|
|
|
background: var(--vp-c-brand);
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 25px;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
2026-01-18 12:21:49 +08:00
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
2026-01-18 10:24:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-btn:hover {
|
|
|
|
|
background: var(--vp-c-brand-dark);
|
|
|
|
|
transform: translateY(-2px);
|
2026-01-18 12:21:49 +08:00
|
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
2026-01-18 10:24:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 19:10:21 +08:00
|
|
|
.fade-enter-active,
|
|
|
|
|
.fade-leave-active {
|
|
|
|
|
transition: opacity 0.3s ease;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 19:10:21 +08:00
|
|
|
.fade-enter-from,
|
|
|
|
|
.fade-leave-to {
|
|
|
|
|
opacity: 0;
|
2026-01-15 20:10:19 +08:00
|
|
|
}
|
|
|
|
|
</style>
|