feat(docs): add JavaScript intro demos and update content structure

refactor(docs): rename "ClaudeCode" to "Claude Code" across all language versions

chore: add ESLint configuration and update build scripts

style: update component organization and remove unused imports
This commit is contained in:
sanbuphy
2026-02-15 18:15:42 +08:00
parent 9c1a395962
commit 9ee3312569
29 changed files with 5168 additions and 1123 deletions
@@ -0,0 +1,477 @@
<script setup>
import { ref } from 'vue'
const mode = ref('sync')
const isRunning = ref(false)
const elapsedTime = ref(0)
const customerA = ref({ time: 2, status: 'waiting' })
const customerB = ref({ time: 3, status: 'waiting' })
const customerC = ref({ time: 5, status: 'waiting' })
const modes = [
{ value: 'sync', label: '同步模式 🐢' },
{ value: 'async', label: '异步模式 ⚡' }
]
const reset = () => {
elapsedTime.value = 0
customerA.value = { time: 2, status: 'waiting' }
customerB.value = { time: 3, status: 'waiting' }
customerC.value = { time: 5, status: 'waiting' }
}
const start = async () => {
if (isRunning.value) return
isRunning.value = true
reset()
if (mode.value === 'sync') {
// 同步模式:依次执行
await processCustomer(customerA, 2000)
await processCustomer(customerB, 3000)
await processCustomer(customerC, 5000)
} else {
// 异步模式:同时执行
await Promise.all([
processCustomer(customerA, 2000),
processCustomer(customerB, 3000),
processCustomer(customerC, 5000)
])
}
isRunning.value = false
}
const processCustomer = async (customer, realTime) => {
customer.status = 'cooking'
await new Promise(resolve => setTimeout(resolve, realTime))
customer.status = 'done'
}
</script>
<template>
<div class="async-restaurant-demo">
<h3>异步同步 vs 异步</h3>
<div class="mode-selector">
<button
v-for="m in modes"
:key="m.value"
@click="mode = m.value"
:class="{ 'active': mode === m.value }"
class="mode-btn"
:disabled="isRunning"
>
{{ m.label }}
</button>
</div>
<div class="restaurant-scene">
<!-- 厨房 -->
<div class="kitchen">
<h4>厨房</h4>
<div class="stoves">
<div
class="stove"
:class="{ 'cooking': customerA.status === 'cooking', 'done': customerA.status === 'done' }"
>
<div class="stove-label">灶位 1</div>
<div class="stove-content">
<div v-if="customerA.status === 'cooking'" class="cooking-text">煮面 {{ customerA.time }}s</div>
<div v-if="customerA.status === 'done'" class="done-text"> 完成</div>
<div v-if="customerA.status === 'waiting'" class="waiting-text">空闲</div>
</div>
</div>
<div
class="stove"
:class="{ 'cooking': customerB.status === 'cooking', 'done': customerB.status === 'done' }"
>
<div class="stove-label">灶位 2</div>
<div class="stove-content">
<div v-if="customerB.status === 'cooking'" class="cooking-text">炒饭 {{ customerB.time }}s</div>
<div v-if="customerB.status === 'done'" class="done-text"> 完成</div>
<div v-if="customerB.status === 'waiting'" class="waiting-text">空闲</div>
</div>
</div>
<div
class="stove"
:class="{ 'cooking': customerC.status === 'cooking', 'done': customerC.status === 'done' }"
>
<div class="stove-label">灶位 3</div>
<div class="stove-content">
<div v-if="customerC.status === 'cooking'" class="cooking-text">烤鱼 {{ customerC.time }}s</div>
<div v-if="customerC.status === 'done'" class="done-text"> 完成</div>
<div v-if="customerC.status === 'waiting'" class="waiting-text">空闲</div>
</div>
</div>
</div>
</div>
<!-- 顾客 -->
<div class="customers">
<h4>顾客</h4>
<div class="customer-list">
<div class="customer" :class="{ 'served': customerA.status === 'done' }">
<div class="customer-avatar">👤</div>
<div class="customer-info">
<div class="customer-name">顾客 A</div>
<div class="customer-order">煮面 ({{ customerA.time }})</div>
</div>
<div v-if="customerA.status === 'done'" class="check-mark"></div>
</div>
<div class="customer" :class="{ 'served': customerB.status === 'done' }">
<div class="customer-avatar">👤</div>
<div class="customer-info">
<div class="customer-name">顾客 B</div>
<div class="customer-order">炒饭 ({{ customerB.time }})</div>
</div>
<div v-if="customerB.status === 'done'" class="check-mark"></div>
</div>
<div class="customer" :class="{ 'served': customerC.status === 'done' }">
<div class="customer-avatar">👤</div>
<div class="customer-info">
<div class="customer-name">顾客 C</div>
<div class="customer-order">烤鱼 ({{ customerC.time }})</div>
</div>
<div v-if="customerC.status === 'done'" class="check-mark"></div>
</div>
</div>
</div>
</div>
<div class="controls">
<button @click="start" :disabled="isRunning" class="btn-start">
{{ isRunning ? '执行中...' : '开始' }}
</button>
<button @click="reset" :disabled="isRunning" class="btn-reset">
重置
</button>
</div>
<div class="comparison" v-if="!isRunning && (customerA.status === 'done' || customerB.status === 'done')">
<div class="comparison-item">
<strong>同步模式</strong> 10 依次执行
</div>
<div class="comparison-item">
<strong>异步模式</strong> 5 同时执行
</div>
<div class="tip">JavaScript 用的就是异步模式遇到耗时操作如网络请求不会傻等而是先去做别的事</div>
</div>
<div class="code-display">
<h4>代码对比</h4>
<div class="code-comparison">
<div class="code-block">
<h5>同步阻塞</h5>
<pre><code>console.log("1")
console.log("2") // 等上面执行完
console.log("3")
// 输出:1, 2, 3</code></pre>
</div>
<div class="code-block">
<h5>异步不阻塞</h5>
<pre><code>console.log("1")
setTimeout(() => console.log("2"), 1000)
console.log("3")
// 输出:1, 3, 2</code></pre>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.async-restaurant-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.mode-selector {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 20px;
}
.mode-btn {
padding: 10px 20px;
border: 2px solid var(--vp-c-border);
border-radius: 8px;
font-size: 14px;
font-weight: 600;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
cursor: pointer;
transition: all 0.2s ease;
}
.mode-btn:hover:not(:disabled) {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-bg-soft);
}
.mode-btn.active {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-brand-1);
color: white;
}
.mode-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.restaurant-scene {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.restaurant-scene {
grid-template-columns: 1fr;
}
}
.kitchen, .customers {
border: 2px solid var(--vp-c-border);
border-radius: 8px;
padding: 16px;
background: var(--vp-c-bg-soft);
}
h4 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.stoves {
display: flex;
flex-direction: column;
gap: 12px;
}
.stove {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border: 2px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg);
transition: all 0.3s ease;
}
.stove.cooking {
border-color: #ed8936;
animation: pulse 1s ease infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(237, 137, 54, 0.4); }
50% { box-shadow: 0 0 0 8px rgba(237, 137, 54, 0); }
}
.stove.done {
border-color: #38a169;
}
.stove-label {
font-size: 12px;
font-weight: 600;
color: var(--vp-c-text-2);
min-width: 60px;
}
.stove-content {
flex: 1;
font-size: 13px;
}
.cooking-text {
color: #ed8936;
font-weight: 500;
}
.done-text {
color: #38a169;
font-weight: 600;
}
.waiting-text {
color: var(--vp-c-text-3);
}
.customer-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.customer {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border: 2px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg);
transition: all 0.3s ease;
}
.customer.served {
border-color: #38a169;
}
.customer-avatar {
font-size: 32px;
}
.customer-info {
flex: 1;
}
.customer-name {
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.customer-order {
font-size: 12px;
color: var(--vp-c-text-2);
margin-top: 4px;
}
.check-mark {
font-size: 24px;
}
.controls {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 20px;
}
button {
padding: 10px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
button:active {
transform: scale(0.95);
}
.btn-start {
background: var(--vp-c-brand-1);
color: white;
}
.btn-start:hover:not(:disabled) {
background: var(--vp-c-brand-2);
}
.btn-start:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-reset {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.btn-reset:hover:not(:disabled) {
background: var(--vp-c-bg-soft-hover);
}
.comparison {
background: var(--vp-c-bg-soft);
border-left: 4px solid var(--vp-c-brand-1);
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
}
.comparison-item {
font-size: 14px;
margin-bottom: 8px;
color: var(--vp-c-text-1);
}
.comparison-item:last-child {
margin-bottom: 0;
}
.tip {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--vp-c-border);
font-size: 13px;
color: var(--vp-c-brand-1);
font-weight: 500;
}
.code-display {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.code-display h4 {
color: #d4d4d4;
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
}
.code-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 640px) {
.code-comparison {
grid-template-columns: 1fr;
}
}
.code-block h5 {
color: #d4d4d4;
margin: 0 0 8px 0;
font-size: 13px;
font-weight: 600;
}
.code-block pre {
margin: 0;
}
.code-block code {
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
color: #d4d4d4;
}
</style>
@@ -0,0 +1,309 @@
<script setup>
import { ref } from 'vue'
const title = ref('我的网页')
const items = ref(['项目1', '项目2'])
const paragraphColor = ref('black')
const modifyTitle = () => {
title.value = 'Hello World!'
}
const addItem = () => {
const id = items.value.length + 1
items.value.push(`新项目${id}`)
}
const changeColor = () => {
paragraphColor.value = paragraphColor.value === 'black' ? 'red' : 'black'
}
const removeItem = () => {
if (items.value.length > 0) {
items.value.pop()
}
}
</script>
<template>
<div class="dom-tree-demo">
<h3>DOM JavaScript 看到的网页</h3>
<div class="demo-container">
<!-- 左侧迷你网页 -->
<div class="webpage-preview">
<div class="browser-bar">
<div class="dots">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
</div>
</div>
<div class="webpage-content">
<h1>{{ title }}</h1>
<p :style="{ color: paragraphColor }">欢迎光临</p>
<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>
</div>
</div>
<!-- 右侧DOM -->
<div class="dom-tree">
<div class="tree-node">
<span class="tag">&lt;html&gt;</span>
<div class="tree-children">
<div class="tree-node">
<span class="tag">&lt;body&gt;</span>
<div class="tree-children">
<div class="tree-node" :class="{ 'active': title === 'Hello World!' }">
<span class="tag">&lt;h1&gt;</span>
<span class="text">{{ title }}</span>
</div>
<div class="tree-node" :class="{ 'active': paragraphColor === 'red' }">
<span class="tag">&lt;p&gt;</span>
<span class="text">欢迎光临</span>
</div>
<div class="tree-node">
<span class="tag">&lt;ul&gt;</span>
<div class="tree-children">
<div class="tree-node" v-for="(item, index) in items" :key="index">
<span class="tag">&lt;li&gt;</span>
<span class="text">{{ item }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="controls">
<button @click="modifyTitle" class="btn-primary">修改标题</button>
<button @click="addItem" class="btn-secondary">添加列表项</button>
<button @click="changeColor" class="btn-secondary">改变段落颜色</button>
<button @click="removeItem" class="btn-danger">删除列表项</button>
</div>
<div class="code-display">
<h4>对应代码</h4>
<pre><code v-if="title === 'Hello World!'">document.querySelector('h1').textContent = '{{ title }}'</code>
<code v-else-if="paragraphColor === 'red'">document.querySelector('p').style.color = '{{ paragraphColor }}'</code>
<code v-else>点击上方按钮查看对应代码</code></pre>
</div>
</div>
</template>
<style scoped>
.dom-tree-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.demo-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.demo-container {
grid-template-columns: 1fr;
}
}
.webpage-preview {
border: 2px solid var(--vp-c-border);
border-radius: 8px;
overflow: hidden;
}
.browser-bar {
background: #f0f0f0;
padding: 8px;
border-bottom: 1px solid var(--vp-c-border);
}
.dots {
display: flex;
gap: 6px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.dot.red {
background: #ff5f56;
}
.dot.yellow {
background: #ffbd2e;
}
.dot.green {
background: #27c93f;
}
.webpage-content {
padding: 16px;
background: white;
color: black;
}
.webpage-content h1 {
margin: 0 0 12px 0;
font-size: 18px;
font-weight: 600;
}
.webpage-content p {
margin: 0 0 12px 0;
font-size: 14px;
}
.webpage-content ul {
margin: 0;
padding-left: 20px;
}
.webpage-content li {
font-size: 14px;
margin-bottom: 4px;
}
.dom-tree {
border: 1px solid var(--vp-c-border);
border-radius: 8px;
padding: 16px;
background: var(--vp-c-bg-soft);
overflow-x: auto;
}
.tree-node {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.tree-children {
margin-left: 20px;
border-left: 2px solid var(--vp-c-border);
padding-left: 12px;
}
.tag {
color: var(--vp-c-brand-1);
font-family: 'Courier New', monospace;
font-size: 13px;
font-weight: 600;
white-space: nowrap;
}
.text {
color: var(--vp-c-text-2);
font-size: 13px;
}
.tree-node.active {
background: rgba(62, 175, 124, 0.1);
border-radius: 4px;
padding: 4px 8px;
animation: highlight 1s ease;
}
@keyframes highlight {
0%, 100% { background: transparent; }
50% { background: rgba(62, 175, 124, 0.2); }
}
.controls {
display: flex;
gap: 8px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 20px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
button:active {
transform: scale(0.95);
}
.btn-primary {
background: var(--vp-c-brand-1);
color: white;
}
.btn-primary:hover {
background: var(--vp-c-brand-2);
}
.btn-secondary {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.btn-secondary:hover {
background: var(--vp-c-bg-soft-hover);
}
.btn-danger {
background: #f56565;
color: white;
}
.btn-danger:hover {
background: #e53e3e;
}
.code-display {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.code-display h4 {
color: #d4d4d4;
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
}
.code-display pre {
margin: 0;
}
.code-display code {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: #d4d4d4;
}
</style>
@@ -0,0 +1,358 @@
<script setup>
import { ref } from 'vue'
const price = ref(100)
const discount = ref(0.8)
const result = ref(null)
const isRunning = ref(false)
const functionType = ref('arrow') // 'declaration', 'expression', 'arrow'
const functionTypes = [
{ value: 'declaration', label: 'function 声明' },
{ value: 'expression', label: '函数表达式' },
{ value: 'arrow', label: '箭头函数' }
]
const execute = async () => {
if (isRunning.value) return
isRunning.value = true
result.value = null
// 模拟处理动画
await new Promise(resolve => setTimeout(resolve, 500))
result.value = price.value * discount.value
isRunning.value = false
}
const currentCode = ref(`const calculatePrice = (price, discount) => {
return price * discount
}`)
</script>
<template>
<div class="function-machine-demo">
<h3>函数就像一台机器</h3>
<div class="pipeline">
<!-- 输入区 -->
<div class="pipeline-section input-section">
<h4>参数输入</h4>
<div class="input-group">
<label>
价格:
<input v-model.number="price" type="number" min="0" :disabled="isRunning" />
</label>
<label>
折扣:
<select v-model.number="discount" :disabled="isRunning">
<option :value="0.8">8 (0.8)</option>
<option :value="0.5">5 (0.5)</option>
<option :value="0.7">7 (0.7)</option>
</select>
</label>
</div>
</div>
<!-- 机器区 -->
<div class="pipeline-section machine-section">
<h4>函数</h4>
<div class="machine">
<div class="machine-label">calculatePrice</div>
<div class="machine-code">
<pre v-if="functionType === 'declaration'"><code>return price * discount</code></pre>
<pre v-else-if="functionType === 'expression'"><code>return price * discount</code></pre>
<pre v-else><code>price * discount</code></pre>
</div>
</div>
<div class="function-type-selector">
<button
v-for="type in functionTypes"
:key="type.value"
@click="functionType = type.value"
:class="{ active: functionType === type.value }"
class="type-btn"
>
{{ type.label }}
</button>
</div>
<div class="tip" v-if="functionType !== 'arrow'"> 写法不同但做的事一模一样</div>
</div>
<!-- 输出区 -->
<div class="pipeline-section output-section">
<h4>返回值输出</h4>
<div class="output-display" :class="{ 'processing': isRunning }">
<div v-if="result === null" class="placeholder">?</div>
<div v-else class="result">¥{{ result.toFixed(2) }}</div>
</div>
</div>
</div>
<div class="controls">
<button @click="execute" :disabled="isRunning" class="btn-execute">
{{ isRunning ? '处理中...' : '执行 ▶' }}
</button>
</div>
<div class="code-display">
<h4>当前函数定义</h4>
<pre><code v-if="functionType === 'declaration'">function calculatePrice(price, discount) {
return price * discount
}</code>
<code v-else-if="functionType === 'expression'">const calculatePrice = function(price, discount) {
return price * discount
}</code>
<code v-else>const calculatePrice = (price, discount) => {
return price * discount
}
// 或者更简洁:
const calculatePrice = (price, discount) => price * discount</code></pre>
</div>
</div>
</template>
<style scoped>
.function-machine-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
h4 {
margin: 0 0 12px 0;
font-size: 13px;
font-weight: 600;
color: var(--vp-c-text-2);
}
.pipeline {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
overflow-x: auto;
}
@media (max-width: 768px) {
.pipeline {
flex-direction: column;
}
}
.pipeline-section {
flex: 1;
min-width: 200px;
padding: 16px;
border-radius: 8px;
background: var(--vp-c-bg-soft);
}
.input-group {
display: flex;
flex-direction: column;
gap: 12px;
}
label {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 13px;
color: var(--vp-c-text-2);
}
input, select {
padding: 6px 8px;
border: 1px solid var(--vp-c-border);
border-radius: 4px;
font-size: 14px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
input:disabled, select:disabled {
opacity: 0.6;
}
.machine {
background: var(--vp-c-brand-1);
border-radius: 8px;
padding: 16px;
color: white;
text-align: center;
}
.machine-label {
font-size: 12px;
font-weight: 600;
margin-bottom: 8px;
opacity: 0.9;
}
.machine-code pre {
margin: 0;
}
.machine-code code {
font-family: 'Courier New', monospace;
font-size: 14px;
color: white;
}
.function-type-selector {
display: flex;
gap: 8px;
margin-top: 12px;
flex-wrap: wrap;
}
.type-btn {
flex: 1;
min-width: 100px;
padding: 6px 12px;
border: 1px solid var(--vp-c-border);
border-radius: 6px;
font-size: 12px;
font-weight: 500;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
cursor: pointer;
transition: all 0.2s ease;
}
.type-btn:hover {
background: var(--vp-c-bg-soft);
}
.type-btn.active {
background: var(--vp-c-brand-1);
color: white;
border-color: var(--vp-c-brand-1);
}
.tip {
margin-top: 8px;
font-size: 12px;
color: var(--vp-c-brand-1);
text-align: center;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.output-display {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg);
font-size: 24px;
font-weight: 600;
font-family: 'Courier New', monospace;
transition: all 0.3s ease;
}
.output-display.processing {
border-color: var(--vp-c-brand-1);
animation: pulse 1s ease infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(62, 175, 124, 0.4); }
50% { box-shadow: 0 0 0 8px rgba(62, 175, 124, 0); }
}
.placeholder {
color: var(--vp-c-text-3);
font-size: 32px;
}
.result {
color: var(--vp-c-brand-1);
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.controls {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.btn-execute {
padding: 10px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
background: var(--vp-c-brand-1);
color: white;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-execute:hover:not(:disabled) {
background: var(--vp-c-brand-2);
transform: scale(1.05);
}
.btn-execute:active:not(:disabled) {
transform: scale(0.95);
}
.btn-execute:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.code-display {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.code-display h4 {
color: #d4d4d4;
margin-bottom: 12px;
}
.code-display pre {
margin: 0;
}
.code-display code {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: #d4d4d4;
}
</style>
@@ -0,0 +1,475 @@
<script setup>
import { ref } from 'vue'
const isPlaying = ref(false)
const currentStep = ref(0)
const codeQueue = ref([
{ id: 1, code: 'console.log("1")', type: 'sync', output: '1' },
{ id: 2, code: 'setTimeout(() => console.log("2"), 0)', type: 'async', output: '2' },
{ id: 3, code: 'console.log("3")', type: 'sync', output: '3' },
{ id: 4, code: 'fetch("/api").then(() => console.log("4"))', type: 'async', output: '4' },
{ id: 5, code: 'console.log("5")', type: 'sync', output: '5' }
])
const taskQueue = ref([])
const outputLog = ref([])
const steps = [
{ description: '执行 console.log("1")', output: '1' },
{ description: '遇到 setTimeout,把回调贴到便签栏', output: null },
{ description: '执行 console.log("3")', output: '3' },
{ description: '遇到 fetch,把回调贴到便签栏', output: null },
{ description: '执行 console.log("5")', output: '5' },
{ description: '执行 setTimeout 的回调', output: '2' },
{ description: '执行 fetch 的回调', output: '4' }
]
const reset = () => {
currentStep.value = 0
taskQueue.value = []
outputLog.value = []
isPlaying.value = false
}
const nextStep = () => {
if (currentStep.value >= steps.length) return
const step = steps[currentStep.value]
if (currentStep.value === 1) {
taskQueue.value.push({ id: 2, code: 'console.log("2")', status: 'pending' })
} else if (currentStep.value === 3) {
taskQueue.value.push({ id: 4, code: 'console.log("4")', status: 'pending' })
} else if (currentStep.value === 4) {
taskQueue.value[0].status = 'ready'
} else if (currentStep.value === 5) {
outputLog.value.push({ output: '2', source: 'setTimeout' })
taskQueue.value.shift()
taskQueue.value[0].status = 'ready'
} else if (currentStep.value === 6) {
outputLog.value.push({ output: '4', source: 'fetch' })
}
if (step.output) {
outputLog.value.push({ output: step.output, source: '同步代码' })
}
currentStep.value++
}
const play = async () => {
if (isPlaying.value) return
isPlaying.value = true
while (currentStep.value < steps.length && isPlaying.value) {
nextStep()
await new Promise(resolve => setTimeout(resolve, 1000))
}
isPlaying.value = false
}
const stop = () => {
isPlaying.value = false
}
</script>
<template>
<div class="event-loop-demo">
<h3>事件循环JavaScript 的执行机制</h3>
<div class="workspace">
<!-- 代码队列 -->
<div class="code-queue-section">
<h4>代码队列</h4>
<div class="queue">
<div
v-for="(item, index) in codeQueue"
:key="item.id"
class="code-item"
:class="{
'active': currentStep === index,
'processed': currentStep > index
}"
>
<div class="item-index">{{ item.id }}</div>
<div class="item-code">{{ item.code }}</div>
<div v-if="currentStep === index" class="executing">执行中</div>
</div>
</div>
</div>
<!-- 工位 -->
<div class="worker-section">
<h4>工位单线程</h4>
<div class="worker">
<div class="worker-emoji">👨💻</div>
<div class="worker-status">
{{ currentStep < steps.length ? '正在执行' : '执行完成' }}
</div>
<div v-if="currentStep < steps.length" class="current-task">
{{ steps[currentStep]?.description }}
</div>
</div>
</div>
<!-- 便签栏 -->
<div class="task-queue-section">
<h4>便签栏任务队列</h4>
<div class="task-queue">
<div
v-for="task in taskQueue"
:key="task.id"
class="task-item"
:class="{ 'ready': task.status === 'ready' }"
>
<div class="task-code">{{ task.code }}</div>
<div class="task-status">
{{ task.status === 'ready' ? '✅ 就绪' : '⏳ 等待中...' }}
</div>
</div>
<div v-if="taskQueue.length === 0" class="empty-queue">
暂无待办任务
</div>
</div>
</div>
</div>
<!-- 输出日志 -->
<div class="output-section">
<h4>输出日志</h4>
<div class="output-log">
<div v-if="outputLog.length === 0" class="empty-log">等待输出...</div>
<div
v-for="(log, index) in outputLog"
:key="index"
class="log-entry"
>
<span class="log-output">{{ log.output }}</span>
<span class="log-source">({{ log.source }})</span>
</div>
</div>
</div>
<!-- 控制按钮 -->
<div class="controls">
<button @click="play" :disabled="isPlaying || currentStep >= steps.length" class="btn-play">
{{ isPlaying ? '执行中...' : '▶ 自动播放' }}
</button>
<button @click="nextStep" :disabled="isPlaying || currentStep >= steps.length" class="btn-step">
单步执行
</button>
<button @click="stop" :disabled="!isPlaying" class="btn-stop">
停止
</button>
<button @click="reset" class="btn-reset">
🔄 重置
</button>
</div>
<!-- 说明 -->
<div class="explanation">
<p><strong>执行顺序</strong>{{ outputLog.map(l => l.output).join(', ') || '还未开始' }}</p>
<p><strong>代码书写顺序</strong>1, 2, 3, 4, 5</p>
<p class="highlight">代码从上到下写的但执行顺序不一定从上到下因为异步操作会被"推迟"到当前代码执行完之后</p>
</div>
</div>
</template>
<style scoped>
.event-loop-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-2);
}
.workspace {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
@media (max-width: 1024px) {
.workspace {
grid-template-columns: 1fr;
}
}
.code-queue-section,
.worker-section,
.task-queue-section {
border: 1px solid var(--vp-c-border);
border-radius: 8px;
padding: 16px;
background: var(--vp-c-bg-soft);
min-height: 300px;
}
.queue,
.task-queue {
display: flex;
flex-direction: column;
gap: 8px;
}
.code-item,
.task-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
border: 2px solid var(--vp-c-border);
border-radius: 6px;
background: var(--vp-c-bg);
transition: all 0.3s ease;
}
.code-item.active {
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 0 3px rgba(62, 175, 124, 0.1);
animation: highlight 1s ease infinite;
}
@keyframes highlight {
0%, 100% { background: var(--vp-c-bg); }
50% { background: rgba(62, 175, 124, 0.1); }
}
.code-item.processed {
opacity: 0.5;
}
.item-index {
font-size: 12px;
font-weight: 600;
color: var(--vp-c-text-2);
min-width: 20px;
}
.item-code,
.task-code {
flex: 1;
font-family: 'Courier New', monospace;
font-size: 12px;
color: var(--vp-c-text-1);
}
.executing {
font-size: 11px;
font-weight: 600;
color: var(--vp-c-brand-1);
white-space: nowrap;
}
.task-item.ready {
border-color: #38a169;
animation: pulse 1s ease infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(56, 161, 105, 0.4); }
50% { box-shadow: 0 0 0 6px rgba(56, 161, 105, 0); }
}
.task-status {
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.empty-queue {
text-align: center;
padding: 20px;
color: var(--vp-c-text-3);
font-size: 13px;
}
.worker {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
}
.worker-emoji {
font-size: 48px;
margin-bottom: 12px;
}
.worker-status {
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 8px;
}
.current-task {
text-align: center;
font-size: 13px;
color: var(--vp-c-text-2);
padding: 8px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.output-section {
margin-bottom: 20px;
}
.output-log {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-height: 60px;
padding: 12px;
border: 1px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg);
}
.empty-log {
color: var(--vp-c-text-3);
font-size: 13px;
}
.log-entry {
padding: 8px 12px;
background: var(--vp-c-brand-1);
color: white;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.log-output {
font-family: 'Courier New', monospace;
}
.log-source {
margin-left: 8px;
font-size: 12px;
opacity: 0.8;
}
.controls {
display: flex;
gap: 8px;
justify-content: center;
margin-bottom: 20px;
flex-wrap: wrap;
}
button {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
button:active {
transform: scale(0.95);
}
.btn-play {
background: var(--vp-c-brand-1);
color: white;
}
.btn-play:hover:not(:disabled) {
background: var(--vp-c-brand-2);
}
.btn-step {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.btn-step:hover:not(:disabled) {
background: var(--vp-c-bg-soft-hover);
}
.btn-stop {
background: #ed8936;
color: white;
}
.btn-stop:hover:not(:disabled) {
background: #dd6b20;
}
.btn-reset {
background: #f56565;
color: white;
}
.btn-reset:hover {
background: #e53e3e;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.explanation {
background: var(--vp-c-bg-soft);
border-left: 4px solid var(--vp-c-brand-1);
border-radius: 8px;
padding: 16px;
}
.explanation p {
margin: 0 0 8px 0;
font-size: 14px;
line-height: 1.6;
color: var(--vp-c-text-1);
}
.explanation p:last-child {
margin-bottom: 0;
}
.explanation strong {
color: var(--vp-c-brand-1);
}
.explanation .highlight {
padding: 12px;
background: rgba(62, 175, 124, 0.1);
border-radius: 6px;
font-weight: 500;
color: var(--vp-c-brand-1);
}
</style>
@@ -0,0 +1,377 @@
<script setup>
import { ref } from 'vue'
// 基本类型
const basicA = ref(10)
const basicB = ref(null)
const basicStep = ref(0)
const basicMessage = ref('')
// 引用类型
const obj1Data = ref({ name: '张三', age: 25 })
const obj2Exists = ref(false)
const obj2Age = ref('')
const refMessage = ref('')
const basicCopy = () => {
basicB.value = basicA.value
basicStep.value = 1
basicMessage.value = '✅ 基本类型复制的是值本身'
}
const basicModify = () => {
if (basicB.value === null) {
basicMessage.value = '⚠️ 请先复制'
return
}
basicB.value = 20
basicMessage.value = '✅ 修改 b 不影响 a'
}
const refCopy = () => {
obj2Exists.value = true
obj2Age.value = obj1Data.value.age
refMessage.value = '⚠️ 两个变量指向同一份数据'
}
const refModify = () => {
if (!obj2Exists.value) {
refMessage.value = '⚠️ 请先复制'
return
}
obj1Data.value.age = 30
obj2Age.value = 30
refMessage.value = '❌ 两个变量指向同一份数据,改了一个另一个也变了!'
}
const refSpreadCopy = () => {
obj2Exists.value = true
obj2Age.value = 25
refMessage.value = '✅ 用展开运算符创建真正的副本,现在互不影响'
}
</script>
<template>
<div class="reference-demo">
<h3> vs 引用</h3>
<div class="demo-container">
<!-- 左侧基本类型 -->
<div class="demo-section basic-section">
<h4>基本类型复制值</h4>
<div class="visualization">
<div class="box" :class="{ 'active': basicA !== null }">
<div class="box-label">a</div>
<div class="box-value">{{ basicA }}</div>
</div>
<div class="arrow" v-if="basicStep >= 1">
<div class="arrow-line"></div>
<div class="arrow-head"></div>
</div>
<div class="box" :class="{ 'active': basicB !== null }" v-if="basicStep >= 1">
<div class="box-label">b</div>
<div class="box-value">{{ basicB }}</div>
</div>
</div>
<div class="message" v-if="basicMessage">{{ basicMessage }}</div>
<div class="controls">
<button @click="basicCopy" class="btn-primary">let b = a复制</button>
<button @click="basicModify" class="btn-secondary">b = 20</button>
</div>
</div>
<!-- 右侧引用类型 -->
<div class="demo-section reference-section">
<h4>引用类型复制地址</h4>
<div class="visualization">
<!-- 数据区 -->
<div class="data-area">
<div class="data-label">数据区</div>
<div class="data-content">
<div>{{ `{ name: "${obj1Data.name}", age: ${obj1Data.age} }` }}</div>
</div>
</div>
<div class="pointers">
<div class="pointer">
<div class="pointer-label">obj1</div>
<div class="arrow-line-down"></div>
</div>
<div class="pointer" v-if="obj2Exists">
<div class="pointer-label">obj2</div>
<div class="arrow-line-down"></div>
</div>
</div>
</div>
<div class="message" v-if="refMessage">{{ refMessage }}</div>
<div class="controls">
<button @click="refCopy" class="btn-primary">let obj2 = obj1复制</button>
<button @click="refModify" class="btn-danger">obj2.age = 30</button>
<button @click="refSpreadCopy" class="btn-success">用展开运算符创建副本</button>
</div>
</div>
</div>
<div class="code-display">
<h4>代码</h4>
<pre><code>// 基本类型
let a = {{ basicA }}
{{ basicB !== null ? `let b = ${basicB}` : '' }}
{{ basicB !== null ? `b = ${basicB}` : '' }}
console.log(a) // {{ basicA }}
// 引用类型
let obj1 = { name: "{{ obj1Data.name }}", age: {{ obj1Data.age }} }
{{ obj2Exists ? 'let obj2 = obj1 // 指向同一份数据' : '' }}
{{ obj2Exists ? `obj2.age = ${obj1Data.age}` : '' }}
console.log(obj1.age) // {{ obj1Data.age }}
</code></pre>
</div>
</div>
</template>
<style scoped>
.reference-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
h4 {
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-2);
}
.demo-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 24px;
}
@media (max-width: 768px) {
.demo-container {
grid-template-columns: 1fr;
}
}
.demo-section {
border: 1px dashed var(--vp-c-border);
border-radius: 8px;
padding: 20px;
background: var(--vp-c-bg-soft);
}
.visualization {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 16px;
min-height: 120px;
}
.box {
width: 80px;
height: 80px;
border: 2px solid var(--vp-c-border);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--vp-c-bg);
transition: all 0.3s ease;
}
.box.active {
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 0 3px rgba(62, 175, 124, 0.1);
}
.box-label {
font-size: 12px;
font-weight: 600;
color: var(--vp-c-text-2);
margin-bottom: 4px;
}
.box-value {
font-size: 24px;
font-weight: 600;
font-family: 'Courier New', monospace;
color: var(--vp-c-brand-1);
}
.arrow {
display: flex;
align-items: center;
gap: 4px;
}
.arrow-line {
width: 40px;
height: 2px;
background: var(--vp-c-brand-1);
}
.arrow-head {
font-size: 24px;
color: var(--vp-c-brand-1);
}
.data-area {
text-align: center;
}
.data-label {
font-size: 12px;
font-weight: 600;
color: var(--vp-c-text-2);
margin-bottom: 8px;
}
.data-content {
border: 2px solid var(--vp-c-brand-1);
border-radius: 8px;
padding: 12px;
background: var(--vp-c-bg);
font-family: 'Courier New', monospace;
font-size: 12px;
color: var(--vp-c-text-1);
}
.pointers {
display: flex;
gap: 24px;
margin-top: 12px;
}
.pointer {
text-align: center;
}
.pointer-label {
font-size: 12px;
font-weight: 600;
color: var(--vp-c-text-2);
margin-bottom: 4px;
}
.arrow-line-down {
width: 2px;
height: 30px;
background: var(--vp-c-brand-1);
margin: 0 auto;
}
.message {
text-align: center;
padding: 8px;
border-radius: 6px;
margin-bottom: 12px;
font-size: 13px;
font-weight: 500;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.controls {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
button {
padding: 6px 12px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
button:active {
transform: scale(0.95);
}
.btn-primary {
background: var(--vp-c-brand-1);
color: white;
}
.btn-primary:hover {
background: var(--vp-c-brand-2);
}
.btn-secondary {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.btn-secondary:hover {
background: var(--vp-c-bg-soft-hover);
}
.btn-danger {
background: #f56565;
color: white;
}
.btn-danger:hover {
background: #e53e3e;
}
.btn-success {
background: #38a169;
color: white;
}
.btn-success:hover {
background: #2f855a;
}
.code-display {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.code-display h4 {
color: #d4d4d4;
margin-bottom: 12px;
}
.code-display pre {
margin: 0;
}
.code-display code {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: #d4d4d4;
}
</style>
@@ -0,0 +1,284 @@
<script setup>
import { ref } from 'vue'
const activeScope = ref('block') // 'global', 'function', 'block'
const explanation = ref('')
const scopes = [
{
id: 'global',
name: '全局作用域',
color: '#a0aec0',
variables: ['appName = "Todo"'],
canSee: ['appName']
},
{
id: 'function',
name: '函数 greet() 作用域',
color: '#4299e1',
variables: ['message = "你好"'],
canSee: ['appName', 'message']
},
{
id: 'block',
name: 'if 块作用域',
color: '#38a169',
variables: ['greeting = message + appName'],
canSee: ['appName', 'message', 'greeting']
}
]
const updateExplanation = () => {
const scope = scopes.find(s => s.id === activeScope.value)
if (scope) {
const visible = scope.canSee.map(v => `${v}`).join('、')
explanation.value = `在这个位置,你能使用这些变量:${visible}`
}
}
updateExplanation()
</script>
<template>
<div class="scope-demo">
<h3>作用域变量的"可见范围"</h3>
<div class="scopes-container">
<!-- 全局作用域 -->
<div
class="scope global-scope"
:class="{ 'active': activeScope === 'global' }"
@click="activeScope = 'global'; updateExplanation()"
>
<div class="scope-header">全局作用域</div>
<div class="scope-content">
<div class="variable" :class="{ 'visible': activeScope === 'global', 'dimmed': activeScope !== 'global' }">
appName = "Todo"
</div>
<!-- 函数作用域 -->
<div class="nested-scope">
<div
class="scope function-scope"
:class="{ 'active': activeScope === 'function' }"
@click.stop="activeScope = 'function'; updateExplanation()"
>
<div class="scope-header">函数 greet() 作用域</div>
<div class="scope-content">
<div class="variable" :class="{ 'visible': ['global', 'function'].includes(activeScope), 'dimmed': !['global', 'function'].includes(activeScope) }">
appName = "Todo"
</div>
<div class="variable" :class="{ 'visible': activeScope === 'function', 'dimmed': activeScope !== 'function' }">
message = "你好"
</div>
<!-- 块级作用域 -->
<div class="nested-scope">
<div
class="scope block-scope"
:class="{ 'active': activeScope === 'block' }"
@click.stop="activeScope = 'block'; updateExplanation()"
>
<div class="scope-header">if 块作用域</div>
<div class="scope-content">
<div class="variable" :class="{ 'visible': true, 'dimmed': false }">
appName = "Todo"
</div>
<div class="variable" :class="{ 'visible': true, 'dimmed': false }">
message = "你好"
</div>
<div class="variable" :class="{ 'visible': activeScope === 'block', 'dimmed': activeScope !== 'block' }">
greeting = message + appName
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="explanation" v-if="explanation">
{{ explanation }}
</div>
<div class="code-display">
<h4>对应代码</h4>
<pre><code>const appName = "Todo" // 全局作用域
function greet() {
const message = "你好" // 函数作用域
if (true) {
const greeting = message + appName // 块级作用域 ✅ 能看到外层的
console.log(greeting)
}
console.log(greeting) // ❌ 报错!外层看不到内层
}</code></pre>
</div>
</div>
</template>
<style scoped>
.scope-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 20px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.scopes-container {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.scope {
border: 3px solid var(--vp-c-border);
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.3s ease;
background: var(--vp-c-bg-soft);
}
.scope:hover {
border-color: var(--vp-c-brand-1);
transform: scale(1.02);
}
.scope.active {
border-width: 4px;
box-shadow: 0 0 0 4px rgba(62, 175, 124, 0.1);
}
.global-scope {
border-color: #a0aec0;
}
.global-scope.active {
border-color: #a0aec0;
box-shadow: 0 0 0 4px rgba(160, 174, 192, 0.2);
}
.function-scope {
border-color: #4299e1;
}
.function-scope.active {
border-color: #4299e1;
box-shadow: 0 0 0 4px rgba(66, 153, 225, 0.2);
}
.block-scope {
border-color: #38a169;
}
.block-scope.active {
border-color: #38a169;
box-shadow: 0 0 0 4px rgba(56, 161, 105, 0.2);
}
.scope-header {
font-size: 12px;
font-weight: 600;
margin-bottom: 12px;
text-align: center;
}
.scope-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.nested-scope {
display: flex;
justify-content: center;
margin-top: 12px;
}
.variable {
padding: 8px 12px;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 12px;
font-weight: 500;
background: var(--vp-c-bg);
transition: all 0.3s ease;
}
.variable.visible {
color: var(--vp-c-text-1);
font-weight: 600;
}
.variable.dimmed {
color: var(--vp-c-text-3);
opacity: 0.5;
}
.explanation {
text-align: center;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
font-weight: 500;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.code-display {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.code-display h4 {
color: #d4d4d4;
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
}
.code-display pre {
margin: 0;
}
.code-display code {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: #d4d4d4;
}
@media (max-width: 768px) {
.scopes-container {
flex-direction: column;
align-items: center;
}
.scope {
min-width: 280px;
}
}
</style>
@@ -0,0 +1,278 @@
<script setup>
import { ref } from 'vue'
const name = ref('张三')
const age = ref(25)
const isStudent = ref(true)
const showMessage = ref('')
const showSuccess = ref(false)
const modifyAge = () => {
age.value = 26
showMessage.value = '✅ let 变量可以修改值'
showSuccess.value = true
setTimeout(() => {
showMessage.value = ''
showSuccess.value = false
}, 2000)
}
const modifyName = () => {
showMessage.value = '❌ const 不能重新赋值'
showSuccess.value = false
setTimeout(() => {
showMessage.value = ''
}, 2000)
}
const reset = () => {
name.value = '张三'
age.value = 25
isStudent.value = true
showMessage.value = ''
}
const codeLines = ref([
`const name = "张三"`,
`let age = 25`,
`const isStudent = true`
])
const executeCode = ref([])
</script>
<template>
<div class="variable-box-demo">
<h3>变量就像带名字的盒子</h3>
<div class="boxes-container">
<!-- 盒子 1: const name -->
<div class="variable-box const-box">
<div class="box-label">const name</div>
<div class="box-value">{{ name }}</div>
<div class="box-icon">🔒</div>
</div>
<!-- 盒子 2: let age -->
<div class="variable-box let-box" :class="{ 'success': showSuccess && age === 26 }">
<div class="box-label">let age</div>
<div class="box-value">{{ age }}</div>
<div class="box-icon">🔓</div>
</div>
<!-- 盒子 3: const isStudent -->
<div class="variable-box const-box">
<div class="box-label">const isStudent</div>
<div class="box-value">{{ isStudent }}</div>
<div class="box-icon">🔒</div>
</div>
</div>
<div class="message-bubble" :class="{ 'error': !showSuccess, 'success': showSuccess }" v-if="showMessage">
{{ showMessage }}
</div>
<div class="controls">
<button @click="modifyAge" class="btn-primary">修改 age 26</button>
<button @click="modifyName" class="btn-danger">修改 name 为李四</button>
<button @click="reset" class="btn-secondary">重置</button>
</div>
<div class="code-display">
<pre><code>const name = "张三"
let age = 25
const isStudent = true
{{ executeCode.join('\n') }}</code></pre>
</div>
</div>
</template>
<style scoped>
.variable-box-demo {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 24px;
margin: 24px 0;
background: var(--vp-c-bg);
}
h3 {
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.boxes-container {
display: flex;
gap: 16px;
justify-content: center;
margin-bottom: 20px;
flex-wrap: wrap;
}
.variable-box {
position: relative;
width: 120px;
height: 120px;
border: 2px solid var(--vp-c-border);
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.variable-box.success {
border-color: #3eaf7c;
animation: pulse 0.5s ease;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.box-label {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
background: var(--vp-c-brand-1);
color: white;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.let-box .box-label {
background: #42b983;
}
.box-value {
font-size: 24px;
font-weight: 600;
font-family: 'Courier New', monospace;
color: var(--vp-c-text-1);
margin-bottom: 8px;
}
.box-icon {
font-size: 16px;
}
.message-bubble {
text-align: center;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
font-weight: 500;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.message-bubble.error {
background: #fee;
color: #c00;
}
.message-bubble.success {
background: #e8f5e9;
color: #2e7d32;
}
.controls {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 20px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
button:active {
transform: scale(0.95);
}
.btn-primary {
background: var(--vp-c-brand-1);
color: white;
}
.btn-primary:hover {
background: var(--vp-c-brand-2);
}
.btn-danger {
background: #f56565;
color: white;
}
.btn-danger:hover {
background: #e53e3e;
}
.btn-secondary {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.btn-secondary:hover {
background: var(--vp-c-bg-soft-hover);
}
.code-display {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
}
.code-display pre {
margin: 0;
}
.code-display code {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: #d4d4d4;
}
@media (max-width: 640px) {
.boxes-container {
flex-direction: column;
align-items: center;
}
.variable-box {
width: 200px;
}
.controls {
flex-direction: column;
}
button {
width: 100%;
}
}
</style>