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:
@@ -209,7 +209,7 @@ Easy-Vibe 通过以下几个阶段,带你从 0 到 1:
|
||||
|
||||
| 章节 | 关键内容 | 状态 |
|
||||
| :------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--- |
|
||||
| [高级一:MCP 与 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
|
||||
| [高级一:MCP 与 Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) | 通过 MCP 与 Skills 扩展 IDE 能力,把外部服务接成工具 | 🚧 |
|
||||
| [高级二:如何让 Coding Tools 长时间工作](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 设计和配置长时间运行的任务,让 Coding Tools 更稳定可靠 | 🚧 |
|
||||
| [高级三:多平台开发:如何构建微信小程序](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程序生态,从官方模板到上线完成一个前端小程序 | ✅ |
|
||||
| [高级四:多平台开发:如何构建微信小程序-包含后端](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程序中接入数据库与后端逻辑,打通完整业务闭环 | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
| الفصل | المحتوى الرئيسي | الحالة |
|
||||
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
|
||||
| [المتقدم 1: MCP ومهارات ClaudeCode](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : توسيع قدرات IDE من خلال MCP والمهارات، توصيل الخدمات الخارجية كأدوات | 🚧 |
|
||||
| [المتقدم 1: MCP ومهارات ClaudeCode](docs/stage-3/core-skills/3.1-mcp-claude-code-skills/) : توسيع قدرات IDE من خلال MCP والمهارات، توصيل الخدمات الخارجية كأدوات | 🚧 |
|
||||
| [المتقدم 2: كيفية جعل Coding Tools تعمل لفترة طويلة](docs/stage-3/core-skills/3.2-long-running-tasks/) : تصميم وتكوين المهام طويلة التشغيل، جعل Coding Tools أكثر استقرارًا وموثوقية | 🚧 |
|
||||
| [المتقدم 3: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : فهم نظام البرامج المصغرة WeChat، إكمال برنامج مصغر للواجهة الأمامية من القالب الرسمي إلى الإطلاق | ✅ |
|
||||
| [المتقدم 4: التطوير متعدد المنصات: كيفية بناء برامج WeChat المصغرة - بما في ذلك الخلفية](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : دمج قاعدة البيانات ومنطق الواجهة الخلفية في البرامج المصغرة، تحقيق دورة نشاط كاملة | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@ Wir glauben, dass durch die Beherrschung von Vibe Coding in Kombination mit syst
|
||||
|
||||
| Kapitel | Schlüsselinhalt | Status |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :----- |
|
||||
| [Fortgeschritten 1: MCP und ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) : IDE-Fähigkeiten durch MCP und Skills erweitern, externe Dienste als Tools anschließen | 🚧 |
|
||||
| [Fortgeschritten 1: MCP und Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) : IDE-Fähigkeiten durch MCP und Skills erweitern, externe Dienste als Tools anschließen | 🚧 |
|
||||
| [Fortgeschritten 2: Coding Tools lange laufen lassen](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) : Lang laufende Aufgaben entwerfen und konfigurieren, Coding Tools stabiler und zuverlässiger machen | 🚧 |
|
||||
| [Fortgeschritten 3: Plattformübergreifende Entwicklung: WeChat-Miniprogramme erstellen](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) : WeChat-Miniprogramm-Ökosystem verstehen, Frontend-Miniprogramm von offizieller Vorlage bis zum Start completed | ✅ |
|
||||
| [Fortgeschritten 4: Plattformübergreifende Entwicklung: WeChat-Miniprogramme erstellen - Mit Backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Datenbank und Backend-Logik in Miniprogramme integrieren, vollständigen Geschäftskreislauf realisieren | 🚧 |
|
||||
|
||||
@@ -143,7 +143,7 @@ We believe that by mastering Vibe Coding combined with systematic training, one
|
||||
|
||||
| Chapter | Key Content | Status |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Advanced 1: MCP and ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | Extend IDE capabilities through MCP and Skills, integrate external services as tools | 🚧 |
|
||||
| [Advanced 1: MCP and Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) | Extend IDE capabilities through MCP and Skills, integrate external services as tools | 🚧 |
|
||||
| [Advanced 2: Making Coding Tools Work for Long Periods](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | Design and configure long-running tasks, make Coding Tools more stable and reliable | 🚧 |
|
||||
| [Advanced 3: Multi-Platform Development: Building WeChat Mini Programs](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | Understand WeChat mini program ecosystem, complete a frontend mini program from official template to launch | ✅ |
|
||||
| [Advanced 4: Multi-Platform Development: Building WeChat Mini Programs - With Backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | Integrate databases and backend logic in mini programs, complete business loops | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@ Creemos que al dominar Vibe Coding y combinarlo con entrenamiento sistemático,
|
||||
|
||||
| Capítulo | Contenido clave | Estado |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------- | :----- |
|
||||
| [Avanzado 1: MCP y ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | Extender capacidades del IDE a través de MCP y Skills, conectar servicios externos como herramientas | 🚧 |
|
||||
| [Avanzado 1: MCP y Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) | Extender capacidades del IDE a través de MCP y Skills, conectar servicios externos como herramientas | 🚧 |
|
||||
| [Avanzado 2: Cómo hacer que Coding Tools funcione durante mucho tiempo](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | Diseñar y configurar tareas de ejecución prolongada, hacer Coding Tools más estable y confiable | 🚧 |
|
||||
| [Avanzado 3: Desarrollo multiplataforma: Cómo construir mini programas de WeChat](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | Entender el ecosistema de mini programas de WeChat, completar un mini programa frontend desde plantilla oficial hasta lanzamiento | ✅ |
|
||||
| [Avanzado 4: Desarrollo multiplataforma: Cómo construir mini programas de WeChat - Incluyendo backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | Integrar base de datos y lógica backend en mini programas,实现 ciclo completo de negocio | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@ Nous croyons qu'en maîtrisant Vibe Coding et en le combinant avec un entraînem
|
||||
|
||||
| Chapitre | Contenu clé | Statut |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------- | :----- |
|
||||
| [Avancé 1 : MCP et ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) : Étendre les capacités de l'IDE via MCP et Skills, connecter des services externes comme outils | 🚧 |
|
||||
| [Avancé 1 : MCP et Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) : Étendre les capacités de l'IDE via MCP et Skills, connecter des services externes comme outils | 🚧 |
|
||||
| [Avancé 2 : Comment faire fonctionner Coding Tools longtemps](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) : Concevoir et configurer des tâches à longue exécution, rendre Coding Tools plus stable et fiable | 🚧 |
|
||||
| [Avancé 3 : Développement multiplateforme : Comment construire des mini-programmes WeChat](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) : Comprendre l'écosystème des mini-programmes WeChat, compléter un mini-programme frontend du modèle officiel au lancement | ✅ |
|
||||
| [Avancé 4 : Développement multiplateforme : Comment construire des mini-programmes WeChat - Y compris backend](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Intégrer une base de données et une logique backend dans les mini-programmes, réaliser un cycle d'activité complet | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@ AI でコードを書こうとしてエラーが続き、諦めかけたくな
|
||||
|
||||
| 章 | 主要内容 | 状態 |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| [上級一:MCP と ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP と Skills を通じて IDE 能力を拡張し、外部サービスをツールとして接続 | 🚧 |
|
||||
| [上級一:MCP と Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) | MCP と Skills を通じて IDE 能力を拡張し、外部サービスをツールとして接続 | 🚧 |
|
||||
| [上級二:Coding Tools を長時間動作させる方法](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 長時間動作するタスクを設計・設定し、Coding Tools をより安定して信頼性の高いものに | 🚧 |
|
||||
| [上級三:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | WeChat ミニプログラムのエコシステムを理解し、公式テンプレートからリリースまでフロントエンドミニプログラムを完成 | ✅ |
|
||||
| [上級四:マルチプラットフォーム開発:WeChat ミニプログラムの構築方法 - バックエンド含む](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | ミニプログラムにデータベースとバックエンドロジックを統合し、完全な業務クローズドループを実現 | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@ AI로 코드를 작성하려고 할 때 오류가 계속 발생하고, 포기하
|
||||
|
||||
| 장 | 주요 내용 | 상태 |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :--- |
|
||||
| [고급 1: MCP와 ClaudeCode Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | MCP와 Skills를 통해 IDE 능력 확장, 외부 서비스를 도구로 연결 | 🚧 |
|
||||
| [고급 1: MCP와 Claude Code Skills](docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) | MCP와 Skills를 통해 IDE 능력 확장, 외부 서비스를 도구로 연결 | 🚧 |
|
||||
| [고급 2: Coding Tools를 장시간 작동시키는 방법](docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 장시간 실행되는 작업 설계 및 구성, Coding Tools를 더 안정적이고 신뢰할 수 있게 | 🚧 |
|
||||
| [고급 3: 멀티플랫폼 개발: 위챗 미니 프로그램 구축 방법](docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 위챗 미니 프로그램 생태계 이해, 공식 템플릿부터 출시까지 프론트엔드 미니 프로그램 완성 | ✅ |
|
||||
| [고급 4: 멀티플랫폼 개발: 위챗 미니 프로그램 구축 방법 - 백엔드 포함](docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 미니 프로그램에 데이터베이스와 백엔드 로직 통합, 완전한 비즈니스 폐루프 실현 | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@ Chúng tôi tin rằng thông qua việc làm chủ Vibe Coding kết hợp vớ
|
||||
|
||||
| Chương | Nội dung chính | Trạng thái |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :--------- |
|
||||
| [Cao cấp 1: MCP và ClaudeCode Skills](docs/stage-3/core-skills/3.1-mcp-claudecode-skills/) : Mở rộng khả năng IDE qua MCP và Skills, kết nối dịch vụ bên ngoài như công cụ | 🚧 |
|
||||
| [Cao cấp 1: MCP và Claude Code Skills](docs/stage-3/core-skills/3.1-mcp-claude-code-skills/) : Mở rộng khả năng IDE qua MCP và Skills, kết nối dịch vụ bên ngoài như công cụ | 🚧 |
|
||||
| [Cao cấp 2: Cách để Coding Tools hoạt động lâu](docs/stage-3/core-skills/3.2-long-running-tasks/) : Thiết kế và cấu hình nhiệm vụ chạy dài, làm Coding Tools ổn định và đáng tin cậy hơn | 🚧 |
|
||||
| [Cao cấp 3: Phát triển đa nền tảng: Cách xây dựng chương trình nhỏ WeChat](docs/stage-3/cross-platform/3.3-wechat-miniprogram/) : Hiểu hệ sinh thái chương trình nhỏ WeChat, hoàn thành chương trình nhỏ frontend từ mẫu chính thức đến phát hành | ✅ |
|
||||
| [Cao cấp 4: Phát triển đa nền tảng: Cách xây dựng chương trình nhỏ WeChat - Bao gồm backend](docs/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) : Tích hợp cơ sở dữ liệu và logic backend vào chương trình nhỏ, thực hiện vòng tuần hoàn hoạt động hoàn chỉnh | 🚧 |
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
| 章节 | 關鍵內容 | 狀態 |
|
||||
| :---------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--- |
|
||||
| [高級一:MCP 與 ClaudeCode Skills](../docs/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/) | 通過 MCP 與 Skills 擴展 IDE 能力,把外部服務接成工具 | 🚧 |
|
||||
| [高級一:MCP 與 Claude Code Skills](../docs/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/) | 通過 MCP 與 Skills 擴展 IDE 能力,把外部服務接成工具 | 🚧 |
|
||||
| [高級二:如何讓 Coding Tools 長時間工作](../docs/zh-cn/stage-3/core-skills/3.2-long-running-tasks/) | 設計和配置長時間運行的任務,讓 Coding Tools 更穩定可靠 | 🚧 |
|
||||
| [高級三:多平台開發:如何建構微信小程式](../docs/zh-cn/stage-3/cross-platform/3.3-wechat-miniprogram/) | 了解微信小程式生態,從官方模板到上線完成一個前端小程式 | ✅ |
|
||||
| [高級四:多平台開發:如何建構微信小程式-包含後端](../docs/zh-cn/stage-3/cross-platform/3.4-wechat-miniprogram-backend/) | 在小程式中接入數據庫與後端邏輯,打通完整業務閉環 | 🚧 |
|
||||
|
||||
@@ -445,8 +445,8 @@ export default defineConfig({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: '高级一:MCP 与 ClaudeCode Skills',
|
||||
link: '/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/'
|
||||
text: '高级一:MCP 与 Claude Code Skills',
|
||||
link: '/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/'
|
||||
},
|
||||
{
|
||||
text: '高级二:如何让 Coding Tools 长时间工作',
|
||||
|
||||
@@ -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"><html></span>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node">
|
||||
<span class="tag"><body></span>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node" :class="{ 'active': title === 'Hello World!' }">
|
||||
<span class="tag"><h1></span>
|
||||
<span class="text">{{ title }}</span>
|
||||
</div>
|
||||
<div class="tree-node" :class="{ 'active': paragraphColor === 'red' }">
|
||||
<span class="tag"><p></span>
|
||||
<span class="text">欢迎光临</span>
|
||||
</div>
|
||||
<div class="tree-node">
|
||||
<span class="tag"><ul></span>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node" v-for="(item, index) in items" :key="index">
|
||||
<span class="tag"><li></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>
|
||||
@@ -480,9 +480,16 @@ import RequestStructureDemo from './components/appendix/api-design/RequestStruct
|
||||
import ResponseStructureDemo from './components/appendix/api-design/ResponseStructureDemo.vue'
|
||||
|
||||
// JavaScript Intro Components
|
||||
import VariableBoxDemo from './components/appendix/javascript-intro/VariableBoxDemo.vue'
|
||||
import ReferenceDemo from './components/appendix/javascript-intro/ReferenceDemo.vue'
|
||||
import FunctionMachineDemo from './components/appendix/javascript-intro/FunctionMachineDemo.vue'
|
||||
import ScopeDemo from './components/appendix/javascript-intro/ScopeDemo.vue'
|
||||
import ClosureDemo from './components/appendix/javascript-intro/ClosureDemo.vue'
|
||||
import DOMTreeDemo from './components/appendix/javascript-intro/DOMTreeDemo.vue'
|
||||
import AsyncRestaurantDemo from './components/appendix/javascript-intro/AsyncRestaurantDemo.vue'
|
||||
import JSEventLoopDemo from './components/appendix/javascript-intro/JSEventLoopDemo.vue'
|
||||
import VariableScopeDemo from './components/appendix/javascript-intro/VariableScopeDemo.vue'
|
||||
import DataTypeDemo from './components/appendix/javascript-intro/DataTypeDemo.vue'
|
||||
import ClosureDemo from './components/appendix/javascript-intro/ClosureDemo.vue'
|
||||
import ThisContextDemo from './components/appendix/javascript-intro/ThisContextDemo.vue'
|
||||
import PrototypeDemo from './components/appendix/javascript-intro/PrototypeDemo.vue'
|
||||
import AsyncDemo from './components/appendix/javascript-intro/AsyncDemo.vue'
|
||||
@@ -642,15 +649,6 @@ export default {
|
||||
app.component('ImperativeVsDeclarativeDemo', ImperativeVsDeclarativeDemo)
|
||||
app.component('ComponentReusabilityDemo', ComponentReusabilityDemo)
|
||||
|
||||
// Frontend Evolution Components Registration
|
||||
app.component('FrontendEvolutionDemo', FrontendEvolutionTimelineDemo)
|
||||
app.component('EvolutionSliceRequestDemo', EvolutionSliceRequestDemo)
|
||||
app.component('EvolutionResponsiveGridDemo', EvolutionResponsiveGridDemo)
|
||||
app.component('EvolutionJQueryVsStateDemo', EvolutionJQueryVsStateDemo)
|
||||
app.component('RoutingModeDemo', EvolutionRoutingModeDemo)
|
||||
app.component('RenderingStrategyDemo', EvolutionRenderingStrategyDemo)
|
||||
app.component('ImperativeVsDeclarativeDemo', EvolutionImperativeVsDeclarativeDemo)
|
||||
|
||||
app.component('BackendEvolutionDemo', BackendEvolutionDemo)
|
||||
app.component('BackendQuickStartDemo', BackendQuickStartDemo)
|
||||
app.component('EvolutionIntroDemo', EvolutionIntroDemo)
|
||||
@@ -975,12 +973,19 @@ export default {
|
||||
app.component('MQComparisonDemo', MQComparisonDemo)
|
||||
|
||||
// JavaScript Intro Components Registration
|
||||
app.component('VariableBoxDemo', VariableBoxDemo)
|
||||
app.component('ReferenceDemo', ReferenceDemo)
|
||||
app.component('FunctionMachineDemo', FunctionMachineDemo)
|
||||
app.component('ScopeDemo', ScopeDemo)
|
||||
app.component('VariableScopeDemo', VariableScopeDemo)
|
||||
app.component('DataTypeDemo', DataTypeDemo)
|
||||
app.component('ClosureDemo', ClosureDemo)
|
||||
app.component('ThisContextDemo', ThisContextDemo)
|
||||
app.component('PrototypeDemo', PrototypeDemo)
|
||||
app.component('AsyncDemo', AsyncDemo)
|
||||
app.component('DOMTreeDemo', DOMTreeDemo)
|
||||
app.component('AsyncRestaurantDemo', AsyncRestaurantDemo)
|
||||
app.component('JSEventLoopDemo', JSEventLoopDemo)
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
|
||||
@@ -10,7 +10,7 @@ Deeply master the MCP protocol and Claude Code advanced techniques to improve de
|
||||
<NavGrid>
|
||||
<NavCard
|
||||
href="#"
|
||||
title="Advanced 1: MCP & ClaudeCode Skills"
|
||||
title="Advanced 1: MCP & Claude Code Skills"
|
||||
description="Master Model Context Protocol (MCP) to extend the capabilities of AI programming tools"
|
||||
/>
|
||||
<NavCard
|
||||
|
||||
@@ -10,7 +10,7 @@ Maîtriser en profondeur le protocole MCP et les techniques avancées de Claude
|
||||
<NavGrid>
|
||||
<NavCard
|
||||
href="#"
|
||||
title="Avancé 1 : Compétences MCP et ClaudeCode"
|
||||
title="Avancé 1 : Compétences MCP et Claude Code"
|
||||
description="Maîtriser Model Context Protocol (MCP) pour étendre les capacités des outils de programmation IA"
|
||||
/>
|
||||
<NavCard
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -722,13 +722,13 @@ RLS 正是为解决这类数据安全与隔离需求而生。它允许开发者
|
||||
|
||||
## 3.1 Clone and Run Supabase Demos
|
||||
|
||||
要开展实操,首先需要获取配套的演示代码仓库。你可以让 Trae 或 ClaudeCode 协助 git clone 以下仓库:https://github.com/THU-SIGS-AIID/Project5-Supabase-Demos
|
||||
要开展实操,首先需要获取配套的演示代码仓库。你可以让 Trae 或 Claude Code 协助 git clone 以下仓库:https://github.com/THU-SIGS-AIID/Project5-Supabase-Demos
|
||||
|
||||
若已配置 SSH 密钥,建议使用 SSH 地址进行 clone(git@github.com:THU-SIGS-AIID/Project5-Supabase-Demos.git)以提升安全性;若 SSH 或 HTTPS 连接遇到网络问题,可以直接点击仓库页面的 “Download ZIP”,获取压缩包后解压即可看到完整代码。
|
||||
|
||||

|
||||
|
||||
Clone 后,你同样可以让 Trae 或者是 ClaudeCode 帮你启动项目,例如直接在 Agent 界面中说明: `帮我直接启动这个项目里面的 project 1 `,或者复制对应想启动 project 的绝对路径,粘贴给大模型让大模型直接启动。
|
||||
Clone 后,你同样可以让 Trae 或者是 Claude Code 帮你启动项目,例如直接在 Agent 界面中说明: `帮我直接启动这个项目里面的 project 1 `,或者复制对应想启动 project 的绝对路径,粘贴给大模型让大模型直接启动。
|
||||
|
||||
## 3.2 Project1 - burger-shop-menu-crud
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# 高级一:MCP 与 ClaudeCode Skills
|
||||
# 高级一:MCP 与 Claude Code Skills
|
||||
|
||||
> 本章节正在编写中,敬请期待...
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<NavGrid>
|
||||
<NavCard
|
||||
href="/easy-vibe/zh-cn/stage-3/core-skills/3.1-mcp-claudecode-skills/"
|
||||
title="高级一:MCP 与 ClaudeCode Skills"
|
||||
href="/easy-vibe/zh-cn/stage-3/core-skills/3.1-mcp-claude-code-skills/"
|
||||
title="高级一:MCP 与 Claude Code Skills"
|
||||
description="掌握 Model Context Protocol (MCP),扩展 AI 编程工具的能力边界"
|
||||
/>
|
||||
<NavCard
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<NavGrid>
|
||||
<NavCard
|
||||
href="#"
|
||||
title="高級一:MCP 與 ClaudeCode Skills"
|
||||
title="高級一:MCP 與 Claude Code Skills"
|
||||
description="掌握 Model Context Protocol (MCP),擴展 AI 編程工具的能力邊界"
|
||||
/>
|
||||
<NavCard
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import js from '@eslint/js'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import vueParser from 'vue-eslint-parser'
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['node_modules/**', 'docs/.vitepress/dist/**', 'docs/.vitepress/cache/**']
|
||||
},
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['**/*.vue', '**/*.js'],
|
||||
languageOptions: {
|
||||
parser: vueParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'vue/no-ref-as-operand': 'error',
|
||||
'vue/no-setup-props-destructure': 'error',
|
||||
'vue/no-template-shadow': 'error',
|
||||
'vue/require-v-for-key': 'error',
|
||||
'vue/no-use-v-if-with-v-for': 'error',
|
||||
'vue/require-valid-default-prop': 'error',
|
||||
'vue/no-mutating-props': 'error',
|
||||
'vue/return-in-computed-property': 'error',
|
||||
'vue/no-side-effects-in-computed-properties': 'error',
|
||||
'vue/no-async-in-computed-properties': 'error',
|
||||
'no-undef': 'error',
|
||||
'no-unused-vars': 'warn',
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
Generated
+1630
-586
File diff suppressed because it is too large
Load Diff
+8
-2
@@ -5,10 +5,12 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vitepress dev docs",
|
||||
"build": "vitepress build docs --force",
|
||||
"build": "npm run lint && vitepress build docs --force",
|
||||
"preview": "vitepress preview docs",
|
||||
"format": "prettier --write .",
|
||||
"verify": "bash scripts/verify.sh"
|
||||
"verify": "bash scripts/verify.sh",
|
||||
"lint": "eslint docs/.vitepress/theme",
|
||||
"lint:fix": "eslint docs/.vitepress/theme --fix"
|
||||
},
|
||||
"keywords": [
|
||||
"easy-vibe",
|
||||
@@ -21,6 +23,10 @@
|
||||
},
|
||||
"license": "CC-BY-NC-SA-4.0",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"vue-eslint-parser": "^9.4.3",
|
||||
"markdown-it-katex": "^2.0.3",
|
||||
"prettier": "^3.7.4"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user