2026-02-03 19:41:14 +08:00
<!--
TcpHandshakeDemo . vue
2026-02-04 16:16:34 +08:00
TCP三次握手演示 - 紧凑交互版
设计理念 :
1. 循循善诱 : 用 "打电话" 的生活场景类比 TCP 连接建立过程 。
2. 紧凑布局 : 保留核心可视化区 , 使用固定底部详情板代替长列表 。
2026-02-03 19:41:14 +08:00
-- >
< template >
2026-02-04 16:16:34 +08:00
< div class = "tcp-compact" >
<!-- 顶部标题与控制 -- >
< div class = "top-bar" >
< div class = "title-section" >
< span class = "app-icon" > 📞 < / span >
< span class = "app-title" > TCP 三次握手 < / span >
< / div >
< div class = "actions" >
< button
class = "action-btn primary"
@click ="nextStep"
: disabled = "currentStep >= 3"
v-if = "currentStep < 3"
>
{ { currentStep === 0 ? '▶ 开始拨号' : '下一步 ➔' } }
< / button >
< button
class = "action-btn outline"
@click ="reset"
>
↺ 重置
< / button >
2026-02-03 19:41:14 +08:00
< / div >
< / div >
2026-02-04 16:16:34 +08:00
<!-- 核心可视化舞台 -- >
< div class = "stage-area" >
<!-- 左侧 : 客户端 / 快递员 -- >
< div class = "actor client" >
< div class = "avatar-box" >
< span class = "avatar-icon" > 🧑 💻 < / span >
< span class = "avatar-label" > 客户端 ( 你 ) < / span >
2026-02-03 19:41:14 +08:00
< / div >
2026-02-04 16:16:34 +08:00
< transition name = "pop" >
< div class = "bubble client" v-if = "currentStep >= 1" >
{{ getBubbleText ( 1 ) }}
2026-02-03 19:41:14 +08:00
< / div >
2026-02-04 16:16:34 +08:00
< / transition >
2026-02-03 19:41:14 +08:00
< / div >
2026-02-04 16:16:34 +08:00
< ! - - 中间 : 连接状态线 - - >
< div class = "connection-line" >
< div class = "line-bg" > < / div >
< div class = "signal-packet" :class = "getSignalClass()" >
< span class = "packet-icon" v-if = "currentStep > 0" > {{ getSignalIcon ( ) }} < / span >
2026-02-03 19:41:14 +08:00
< / div >
2026-02-04 16:16:34 +08:00
< div class = "status-badge" : class = "{ connected: currentStep === 3 }" >
{ { currentStep === 3 ? '✅ 连接建立' : '⏳ 连接中...' } }
2026-02-03 19:41:14 +08:00
< / div >
< / div >
2026-02-04 16:16:34 +08:00
<!-- 右侧 : 服务器 / 收件人 -- >
< div class = "actor server" >
< div class = "avatar-box" >
< span class = "avatar-icon" > 🖥 ️ < / span >
< span class = "avatar-label" > 服务器 < / span >
2026-02-03 19:41:14 +08:00
< / div >
2026-02-04 16:16:34 +08:00
< transition name = "pop" >
< div class = "bubble server" v-if = "currentStep >= 2" >
{{ getBubbleText ( 2 ) }}
2026-02-03 19:41:14 +08:00
< / div >
2026-02-04 16:16:34 +08:00
< / transition >
2026-02-03 19:41:14 +08:00
< / div >
< / div >
2026-02-04 16:16:34 +08:00
< ! - - 步骤进度条 ( 可点击跳转 ) - - >
< div class = "step-indicator" >
< div
v-for = "(step, index) in steps"
2026-02-03 19:41:14 +08:00
:key = "index"
2026-02-04 16:16:34 +08:00
class = "step-dot"
: class = "{ active: currentStep === index + 1, passed: currentStep > index + 1 }"
2026-02-03 19:41:14 +08:00
@click ="goToStep(index + 1)"
2026-02-04 16:16:34 +08:00
:title = "step.techTitle"
2026-02-03 19:41:14 +08:00
>
2026-02-04 16:16:34 +08:00
< span class = "dot-num" > { { index + 1 } } < / span >
< span class = "dot-line" v-if = "index < steps.length - 1" > < / span >
2026-02-03 19:41:14 +08:00
< / div >
< / div >
2026-02-04 16:16:34 +08:00
< ! - - 底部详情面板 ( 固定高度 ) - - >
< div class = "detail-panel" >
< transition name = "fade" mode = "out-in" >
< div v-if = "currentStep > 0" class="detail-content" :key="currentStep" >
< div class = "detail-left" : style = "{ borderColor: getCurrentStepColor() }" >
< div class = "step-badge" : style = "{ background: getCurrentStepColor() }" >
步骤 { { currentStep } }
< / div >
< / div >
< div class = "detail-divider" > < / div >
< div class = "detail-right" >
< div class = "info-row" >
< span class = "tag life" > 生活对话 < / span >
< span class = "text highlight" > { { steps [ currentStep - 1 ] . simpleTitle } } < / span >
< / div >
< div class = "info-row" >
< span class = "tag tech" > 技术原理 < / span >
< div class = "tech-content" >
< div class = "tech-desc" > { { steps [ currentStep - 1 ] . techDesc } } < / div >
<!-- 动态名词解码卡片 -- >
< div class = "term-glossary" >
< div v-for = "term in steps[currentStep-1].terms" :key="term.key" class="term-item" >
< span class = "term-key" > { { term . key } } < / span >
< span class = "term-val" > { { term . val } } < / span >
< / div >
< / div >
<!-- 代码实现细节 ( 折叠 ) -- >
< details class = "code-details" v-if = "steps[currentStep-1].codeImpl" >
< summary class = "code-summary" >
< span class = "summary-icon" > 🛠 ️ < / span >
< span class = "summary-text" > 技术深究 : 底层代码如何实现 ? < / span >
< / summary >
< div class = "code-block-wrapper" >
< div class = "code-title" > { { steps [ currentStep - 1 ] . codeImpl . title } } < / div >
< pre class = "code-block" > < code v-html = "steps[currentStep-1].codeImpl.code" > < / code > < / pre >
< / div >
< / details >
< ! - - 技术问答 ( 折叠 ) - 仅在有问答时显示 - - >
< details class = "code-details qa-details" v-if = "steps[currentStep-1].qa" >
< summary class = "code-summary qa-summary" >
< span class = "summary-icon" > 🎓 < / span >
< span class = "summary-text" > { { steps [ currentStep - 1 ] . qa . title } } < / span >
< / summary >
< div class = "code-block-wrapper qa-content" >
< div v-for = "(item, idx) in steps[currentStep-1].qa.content" :key="idx" class="qa-item" >
< div class = "qa-q" > Q : { { item . q } } < / div >
< div class = "qa-a" v-html = "item.a" > < / div >
< / div >
< / div >
< / details >
< / div >
< / div >
< / div >
< ! - - 下一步按钮 - - >
< button
class = "next-btn"
v-if = "currentStep < 3"
@click ="nextStep"
>
下一步 ➔
< / button >
< / div >
< div v-else class = "detail-placeholder" >
< span class = "guide-bounce" > 📞 < / span >
< span > 点击 "开始拨号" 或步骤圆点 , 开始拨打电话 < / span >
< / div >
< / transition >
2026-02-03 19:41:14 +08:00
< / div >
< / div >
< / template >
< script setup >
import { ref } from 'vue'
const currentStep = ref ( 0 )
const steps = [
{
2026-02-04 16:16:34 +08:00
simpleTitle : '喂,在家吗?我是快递员!' ,
techTitle : 'SYN' ,
techDesc : '客户端发送 SYN 包 (Seq=x),请求建立连接。' ,
color : '#3b82f6' ,
terms : [
{ key : 'SYN' , val : '是单词 Synchronize (同步) 的缩写。💡 为什么叫"同步"?因为建立连接的第一步,就是双方要"对表",把暗号(序号)对齐,确保后续对话在同一个频道上。' } ,
{ key : 'Seq=x' , val : '意思是:"我的数据计数器,从 x 开始"。💡 为什么要特意告诉对方?这就像是两个人约定"暗号"。我告诉你:"我的暗号是从 x 开始算的"。以后我每发给你一个字,暗号就加 1。如果你不知道我的起始暗号是 x,以后收到 x+100 你就不知道它是第几个字,也没法判断中间有没有丢字。' }
] ,
codeImpl : {
title : '💻 真实 TCP 报文构建 (伪代码)' ,
code : ` // 1. 设置标志位:只置 SYN
<span class="kw">tcph->syn</span> = 1;
<span class="kw">tcph->ack</span> = 0;
// 2. 生成随机序号 (Seq)
// 操作系统内核会维护一个计数器
// 这里的 htonl 是为了处理网络字节序
<span class="kw">tcph->seq</span> = htonl(<span class="var">random_x</span>);
// 3. 发送数据包
send_packet(client_socket, tcph); `
}
2026-02-03 19:41:14 +08:00
} ,
{
2026-02-04 16:16:34 +08:00
simpleTitle : '在的!我听到了,请说!' ,
techTitle : 'SYN-ACK' ,
techDesc : '服务器回复 SYN-ACK 包 (Seq=y, Ack=x+1),确认并请求连接。' ,
color : '#10b981' ,
terms : [
{ key : 'ACK' , val : '是单词 Acknowledgment (确认) 的缩写。💡 就像快递签收单,表示"我收到你的请求了"。' } ,
{ key : 'Ack=x+1' , val : '确认号。💡 为什么要 +1?这是一种期待机制。意思是:"x 号那一页我已经收好了,请你下次从 x+1 页开始讲"。' } ,
{ key : 'Seq=y' , val : '服务器也生成自己的随机序号 y,方便客户端确认服务器发来的话。' }
] ,
codeImpl : {
title : '💻 服务器内核响应逻辑 (伪代码)' ,
code : ` // 1. 检查收到的是否是 SYN
if (<span class="kw">recv_tcph->syn</span> == 1) {
// 2. 准备回复包
<span class="kw">reply_tcph->syn</span> = 1; // 同步
<span class="kw">reply_tcph->ack</span> = 1; // 确认
// 3. 确认号 = 对方 Seq + 1
// 表示期待对方下一次发在这个序号之后的数据
<span class="kw">reply_tcph->ack_seq</span> = htonl(ntohl(<span class="var">recv_tcph->seq</span>) + 1);
// 4. 生成服务器自己的序号
<span class="kw">reply_tcph->seq</span> = htonl(<span class="var">random_y</span>);
send_packet(server_socket, reply_tcph);
} `
}
2026-02-03 19:41:14 +08:00
} ,
{
2026-02-04 16:16:34 +08:00
simpleTitle : '好的,那我开始说了!' ,
techTitle : 'ACK' ,
techDesc : '客户端发送 ACK 包 (Ack=y+1),连接建立成功,可以传输数据。' ,
color : '#8b5cf6' ,
terms : [
{ key : 'Ack=y+1' , val : '客户端确认收到。意思是:"服务器你的 y 号信我也收到了,我们正式开始聊天吧!"' } ,
{ key : '连接建立' , val : '双方都确认了对方"能听能说",通道正式打通。' }
] ,
codeImpl : {
title : '💻 客户端最终确认 (伪代码)' ,
code : ` // 1. 检查收到的包
if (<span class="kw">recv_tcph->syn</span> == 1 && <span class="kw">recv_tcph->ack</span> == 1) {
// 2. 准备 ACK 包
<span class="kw">ack_tcph->syn</span> = 0; // 第三次握手不需要 SYN 了
<span class="kw">ack_tcph->ack</span> = 1;
// 3. 确认号 = 服务器 Seq + 1
<span class="kw">ack_tcph->ack_seq</span> = htonl(ntohl(<span class="var">recv_tcph->seq</span>) + 1);
// 4. 序号 = 自己的 Seq + 1
<span class="kw">ack_tcph->seq</span> = htonl(<span class="var">my_seq</span> + 1);
// 5. 连接状态变为 ESTABLISHED
<span class="hl">socket->state = TCP_ESTABLISHED;</span>
send_packet(client_socket, ack_tcph);
} `
} ,
qa : {
title : '🤔 为什么必须是三次?(核心逻辑)' ,
content : [
{
q : '为什么一定要三次?(双工确认原理)' ,
a : ` 这其实是在验证<strong>双方的"听说能力"</strong>是否正常。TCP 是全双工的(双方都能同时发和收),所以必须双方都确认对方能发能收:<br>
1️⃣ <strong>第一次 (Client -> Server)</strong>: Server 收到,证明 <strong>Client 能发</strong>, <strong>Server 能收</strong>。<br>
2️⃣ <strong>第二次 (Server -> Client)</strong>: Client 收到,证明 <strong>Server 能发</strong>, <strong>Client 能收</strong>。同时 Client 知道 Server 收到了自己的第一次请求。<br>
3️⃣ <strong>第三次 (Client -> Server)</strong>: Server 收到,证明 <strong>Client 能收</strong>(因为 Client 回复了 Server 的消息)。<br>
<br>
<strong>结论:</strong> 只有经过这三次,双方都明确知道"自己"和"对方"的发送、接收功能全是好的。少一次都不行(Server 不知道 Client 能不能收),多一次没必要。 `
} ,
{
q : '为什么这就算"连上"了?' ,
a : ` 所谓的"连接建立",在计算机里并不是真的拉了一根线。它的本质是:<strong>双方内存里都保存好了对方的"状态信息"</strong>。<br>
通过这三次握手,双方主要完成了两件事:<br>
1. <strong>确认通道畅通</strong>:就是上面说的双工能力确认。<br>
2. <strong>同步初始序号 (ISN)</strong>:双方交换了 Seq (x 和 y)。<br>
<br>
只要双方都记住了对方的 Seq,并且确认了对方在线,操作系统就会把 Socket 状态标记为 <code style="color:#10b981">ESTABLISHED</code> (已建立),这就叫"连上了"。 `
}
]
}
2026-02-03 19:41:14 +08:00
}
]
const nextStep = ( ) => {
2026-02-04 16:16:34 +08:00
if ( currentStep . value < 3 ) currentStep . value ++
2026-02-03 19:41:14 +08:00
}
const goToStep = ( step ) => {
currentStep . value = step
}
const reset = ( ) => {
currentStep . value = 0
}
2026-02-04 16:16:34 +08:00
const getBubbleText = ( stepIndex ) => {
// stepIndex 1: Client speaks (Step 1)
// stepIndex 2: Server speaks (Step 2)
if ( stepIndex === 1 ) return steps [ 0 ] . simpleTitle
if ( stepIndex === 2 ) return steps [ 1 ] . simpleTitle
return ''
}
const getSignalClass = ( ) => {
if ( currentStep . value === 1 ) return 'sending' // Left to Right
if ( currentStep . value === 2 ) return 'receiving' // Right to Left
if ( currentStep . value === 3 ) return 'sending-final' // Left to Right
return ''
}
const getSignalIcon = ( ) => {
return '🔔'
}
const getCurrentStepColor = ( ) => {
return steps [ currentStep . value - 1 ] ? . color || '#ccc'
}
2026-02-03 19:41:14 +08:00
< / script >
< style scoped >
2026-02-04 16:16:34 +08:00
. tcp - compact {
border : 1 px solid var ( -- vp - c - divider ) ;
2026-02-14 20:23:34 +08:00
border - radius : 6 px ;
2026-02-04 16:16:34 +08:00
background : var ( -- vp - c - bg ) ;
padding : 16 px ;
margin : 16 px 0 ;
font - size : 14 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. top - bar {
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
justify - content : space - between ;
2026-02-03 19:41:14 +08:00
align - items : center ;
margin - bottom : 20 px ;
}
2026-02-04 16:16:34 +08:00
. title - section {
display : flex ;
align - items : center ;
gap : 8 px ;
2026-02-03 19:41:14 +08:00
font - weight : 600 ;
color : var ( -- vp - c - text - 1 ) ;
}
2026-02-04 16:16:34 +08:00
. actions {
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
gap : 8 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. action - btn {
padding : 4 px 12 px ;
border - radius : 4 px ;
font - size : 12 px ;
2026-02-03 19:41:14 +08:00
cursor : pointer ;
2026-02-04 16:16:34 +08:00
transition : all 0.2 s ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. action - btn . primary {
2026-02-03 19:41:14 +08:00
background : var ( -- vp - c - brand ) ;
color : white ;
2026-02-04 16:16:34 +08:00
border : 1 px solid var ( -- vp - c - brand ) ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. action - btn . outline {
background : transparent ;
border : 1 px solid var ( -- vp - c - divider ) ;
color : var ( -- vp - c - text - 2 ) ;
}
/* 舞台区 */
. stage - area {
2026-02-03 19:41:14 +08:00
display : flex ;
justify - content : space - between ;
2026-02-04 16:16:34 +08:00
align - items : center ;
height : 140 px ;
background : var ( -- vp - c - bg - soft ) ;
2026-02-14 20:23:34 +08:00
border - radius : 6 px ;
2026-02-04 16:16:34 +08:00
padding : 0 30 px ;
position : relative ;
2026-02-03 19:41:14 +08:00
margin - bottom : 20 px ;
}
2026-02-04 16:16:34 +08:00
. actor {
2026-02-03 19:41:14 +08:00
display : flex ;
flex - direction : column ;
align - items : center ;
2026-02-04 16:16:34 +08:00
width : 120 px ;
position : relative ;
2026-02-03 19:41:14 +08:00
}
. avatar - box {
display : flex ;
2026-02-04 16:16:34 +08:00
flex - direction : column ;
2026-02-03 19:41:14 +08:00
align - items : center ;
2026-02-04 16:16:34 +08:00
z - index : 2 ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. avatar - icon { font - size : 32 px ; }
. avatar - label { font - size : 12 px ; color : var ( -- vp - c - text - 2 ) ; margin - top : 4 px ; }
/* 气泡 */
. bubble {
position : absolute ;
top : - 40 px ;
background : white ;
padding : 6 px 10 px ;
2026-02-03 19:41:14 +08:00
border - radius : 12 px ;
font - size : 12 px ;
2026-02-04 16:16:34 +08:00
box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.1 ) ;
white - space : nowrap ;
border : 1 px solid var ( -- vp - c - divider ) ;
color : # 333 ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. bubble . client { left : 50 % ; transform : translateX ( - 50 % ) ; }
. bubble . server { left : 50 % ; transform : translateX ( - 50 % ) ; }
. pop - enter - active , . pop - leave - active { transition : all 0.3 s cubic - bezier ( 0.175 , 0.885 , 0.32 , 1.275 ) ; }
. pop - enter - from , . pop - leave - to { opacity : 0 ; transform : translateX ( - 50 % ) scale ( 0.8 ) ; }
2026-02-03 19:41:14 +08:00
2026-02-04 16:16:34 +08:00
/* 连接线 */
. connection - line {
flex : 1 ;
height : 2 px ;
background : var ( -- vp - c - divider ) ;
margin : 0 20 px ;
position : relative ;
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
justify - content : center ;
2026-02-03 19:41:14 +08:00
align - items : center ;
}
2026-02-04 16:16:34 +08:00
. status - badge {
position : absolute ;
top : 15 px ;
font - size : 12 px ;
color : var ( -- vp - c - text - 3 ) ;
transition : all 0.3 s ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. status - badge . connected { color : var ( -- vp - c - brand ) ; font - weight : bold ; }
. signal - packet {
position : absolute ;
width : 24 px ;
height : 24 px ;
background : var ( -- vp - c - brand ) ;
2026-02-03 19:41:14 +08:00
border - radius : 50 % ;
display : flex ;
align - items : center ;
justify - content : center ;
color : white ;
2026-02-04 16:16:34 +08:00
font - size : 12 px ;
opacity : 0 ;
top : - 11 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. signal - packet . sending {
animation : moveRight 1.5 s forwards ;
2026-02-03 19:41:14 +08:00
opacity : 1 ;
}
2026-02-04 16:16:34 +08:00
. signal - packet . receiving {
animation : moveLeft 1.5 s forwards ;
opacity : 1 ;
background : # 10 b981 ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. signal - packet . sending - final {
animation : moveRight 1.5 s forwards ;
opacity : 1 ;
background : # 8 b5cf6 ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
@ keyframes moveRight {
0 % { left : 0 ; }
100 % { left : 100 % ; }
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
@ keyframes moveLeft {
0 % { left : 100 % ; }
100 % { left : 0 ; }
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
/* 步骤指示器 */
. step - indicator {
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
justify - content : center ;
gap : 40 px ;
2026-02-03 19:41:14 +08:00
margin - bottom : 20 px ;
}
2026-02-04 16:16:34 +08:00
. step - dot {
width : 24 px ;
height : 24 px ;
border - radius : 50 % ;
background : var ( -- vp - c - bg - alt ) ;
2026-02-03 19:41:14 +08:00
border : 2 px solid var ( -- vp - c - divider ) ;
2026-02-04 16:16:34 +08:00
display : flex ;
align - items : center ;
justify - content : center ;
font - size : 12 px ;
color : var ( -- vp - c - text - 3 ) ;
2026-02-03 19:41:14 +08:00
cursor : pointer ;
2026-02-04 16:16:34 +08:00
position : relative ;
2026-02-03 19:41:14 +08:00
transition : all 0.3 s ;
}
2026-02-04 16:16:34 +08:00
. step - dot . active {
2026-02-03 19:41:14 +08:00
border - color : var ( -- vp - c - brand ) ;
2026-02-04 16:16:34 +08:00
background : var ( -- vp - c - brand ) ;
color : white ;
transform : scale ( 1.1 ) ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. step - dot . passed {
border - color : var ( -- vp - c - brand ) ;
color : var ( -- vp - c - brand ) ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. dot - line {
position : absolute ;
left : 24 px ;
width : 40 px ;
height : 2 px ;
2026-02-03 19:41:14 +08:00
background : var ( -- vp - c - divider ) ;
2026-02-04 16:16:34 +08:00
}
/* 详情面板 */
. detail - panel {
min - height : 80 px ; /* 改为最小高度 */
background : var ( -- vp - c - bg - alt ) ;
2026-02-14 20:23:34 +08:00
border - radius : 6 px ;
2026-02-04 16:16:34 +08:00
border : 1 px solid var ( -- vp - c - divider ) ;
padding : 12 px 16 px ;
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
align - items : flex - start ; /* 顶部对齐 */
/* overflow: hidden; 移除隐藏 */
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. detail - content {
display : flex ;
width : 100 % ;
align - items : flex - start ; /* 顶部对齐 */
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. detail - left {
padding - right : 16 px ;
border - right : 2 px solid transparent ;
margin - top : 4 px ; /* 微调对齐 */
}
. step - badge {
padding : 4 px 8 px ;
border - radius : 4 px ;
2026-02-03 19:41:14 +08:00
color : white ;
2026-02-04 16:16:34 +08:00
font - size : 12 px ;
font - weight : bold ;
white - space : nowrap ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. detail - divider {
width : 1 px ;
align - self : stretch ; /* 拉伸高度 */
background : var ( -- vp - c - divider ) ;
margin : 0 16 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. detail - right {
flex : 1 ;
display : flex ;
flex - direction : column ;
gap : 8 px ; /* 增加间距 */
padding - bottom : 4 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. info - row {
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
align - items : flex - start ; /* 顶部对齐 */
gap : 8 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. tag {
font - size : 11 px ;
padding : 2 px 6 px ;
border - radius : 3 px ;
white - space : nowrap ;
margin - top : 2 px ;
}
. tag . life { background : # e6f7ff ; color : # 1890 ff ; }
. tag . tech { background : # f6ffed ; color : # 52 c41a ; }
. text {
2026-02-03 19:41:14 +08:00
font - size : 13 px ;
2026-02-04 16:16:34 +08:00
color : var ( -- vp - c - text - 1 ) ;
line - height : 1.5 ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. text . highlight {
font - weight : 500 ;
2026-02-03 19:41:14 +08:00
color : var ( -- vp - c - brand ) ;
}
2026-02-04 16:16:34 +08:00
/* 新增:术语解释样式 */
. term - glossary {
margin - top : 8 px ;
display : flex ;
flex - direction : column ;
gap : 6 px ;
background : rgba ( 0 , 0 , 0 , 0.03 ) ;
padding : 8 px ;
border - radius : 6 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. term - item {
font - size : 12 px ;
line - height : 1.5 ;
color : var ( -- vp - c - text - 2 ) ;
}
. term - key {
font - weight : bold ;
color : var ( -- vp - c - brand - dark ) ;
margin - right : 6 px ;
background : rgba ( var ( -- vp - c - brand - rgb ) , 0.1 ) ;
padding : 0 4 px ;
border - radius : 3 px ;
}
. next - btn {
padding : 6 px 16 px ;
2026-02-03 19:41:14 +08:00
background : var ( -- vp - c - brand ) ;
color : white ;
2026-02-04 16:16:34 +08:00
border - radius : 4 px ;
font - size : 12 px ;
cursor : pointer ;
margin - left : 12 px ;
margin - top : 4 px ;
white - space : nowrap ;
align - self : flex - start ; /* 按钮顶部对齐 */
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. detail - placeholder {
2026-02-03 19:41:14 +08:00
display : flex ;
2026-02-04 16:16:34 +08:00
align - items : center ;
gap : 8 px ;
color : var ( -- vp - c - text - 3 ) ;
width : 100 % ;
justify - content : center ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. guide - bounce {
animation : bounce 1.5 s infinite ;
}
@ keyframes bounce {
0 % , 100 % { transform : translateY ( 0 ) ; }
50 % { transform : translateY ( - 3 px ) ; }
}
/* 代码实现折叠块 */
. code - details {
margin - top : 12 px ;
border : 1 px solid var ( -- vp - c - divider ) ;
border - radius : 6 px ;
overflow : hidden ;
background : var ( -- vp - c - bg - alt ) ;
}
. code - summary {
padding : 8 px 12 px ;
cursor : pointer ;
display : flex ;
align - items : center ;
gap : 6 px ;
2026-02-03 19:41:14 +08:00
font - size : 12 px ;
2026-02-04 16:16:34 +08:00
font - weight : 500 ;
color : var ( -- vp - c - text - 2 ) ;
user - select : none ;
background : rgba ( 0 , 0 , 0 , 0.02 ) ;
transition : background 0.2 s ;
}
. code - summary : hover {
background : rgba ( 0 , 0 , 0 , 0.05 ) ;
2026-02-03 19:41:14 +08:00
color : var ( -- vp - c - brand ) ;
}
2026-02-04 16:16:34 +08:00
. code - block - wrapper {
padding : 12 px ;
border - top : 1 px solid var ( -- vp - c - divider ) ;
background : # 282 c34 ; /* 深色背景适合代码 */
color : # abb2bf ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. code - title {
font - size : 11 px ;
color : # 61 afef ;
margin - bottom : 6 px ;
font - family : monospace ;
font - weight : bold ;
}
. code - block {
margin : 0 ;
font - family : var ( -- vp - font - family - mono ) ;
font - size : 11 px ;
line - height : 1.5 ;
white - space : pre - wrap ;
word - break : break - all ;
}
/* 语法高亮类 (深色模式) */
: deep ( . kw ) { color : # c678dd ; } /* 紫色 - 关键字/字段 */
: deep ( . var ) { color : # d19a66 ; } /* 橙色 - 变量 */
: deep ( . hl ) { color : # 98 c379 ; font - weight : bold ; } /* 绿色 - 高亮行 */
/* 问答折叠块 */
. qa - details {
background : rgba ( 255 , 165 , 0 , 0.05 ) ; /* 淡淡的橙色背景 */
border - color : rgba ( 255 , 165 , 0 , 0.2 ) ;
}
. qa - summary {
color : # d46b08 ;
}
. qa - summary : hover {
color : # ff7a45 ;
background : rgba ( 255 , 165 , 0 , 0.1 ) ;
}
. qa - content {
background : var ( -- vp - c - bg ) ; /* 恢复浅色/深色背景 */
2026-02-03 19:41:14 +08:00
color : var ( -- vp - c - text - 1 ) ;
2026-02-04 16:16:34 +08:00
padding : 16 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. qa - item {
margin - bottom : 12 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. qa - item : last - child { margin - bottom : 0 ; }
2026-02-03 19:41:14 +08:00
2026-02-04 16:16:34 +08:00
. qa - q {
font - weight : bold ;
font - size : 13 px ;
color : var ( -- vp - c - brand - dark ) ;
margin - bottom : 4 px ;
2026-02-03 19:41:14 +08:00
}
2026-02-04 16:16:34 +08:00
. qa - a {
font - size : 13 px ;
color : var ( -- vp - c - text - 2 ) ;
line - height : 1.6 ;
2026-02-03 19:41:14 +08:00
}
< / style >