feat(docs): enhance interactive demos and improve documentation
- Add new interactive components for frontend routing, browser rendering pipeline, and database transactions - Improve existing demos with better visuals, explanations, and examples - Update documentation structure and content for better clarity - Add new utility scripts and update package.json with new commands - Fix formatting and alignment in documentation tables
This commit is contained in:
+267
-535
@@ -1,5 +1,15 @@
|
||||
<template>
|
||||
<div class="backend-languages-demo">
|
||||
<div class="demo-header">
|
||||
<span class="icon">🛠️</span>
|
||||
<span class="title">后端语言工具箱</span>
|
||||
<span class="subtitle">选择合适的工具完成工作</span>
|
||||
</div>
|
||||
|
||||
<div class="intro-text">
|
||||
想象你是一名<span class="highlight">建筑工人</span>:搬砖用铁铲,砌墙用瓦刀,装修用刷子。后端语言也一样,不同场景适合不同的"工具"。没有最好的语言,只有最合适的选择。
|
||||
</div>
|
||||
|
||||
<div class="language-grid">
|
||||
<div
|
||||
v-for="lang in languages"
|
||||
@@ -8,470 +18,286 @@
|
||||
:class="{ active: selectedLang === lang.name }"
|
||||
@click="selectedLang = lang.name"
|
||||
>
|
||||
<div class="lang-header">
|
||||
<div class="lang-icon">{{ lang.icon }}</div>
|
||||
<h4>{{ lang.name }}</h4>
|
||||
<div class="lang-year">{{ lang.year }}</div>
|
||||
</div>
|
||||
<div class="lang-tags">
|
||||
<span
|
||||
v-for="tag in lang.tags"
|
||||
:key="tag"
|
||||
class="tag"
|
||||
:class="`tag-${tag.type}`"
|
||||
>
|
||||
{{ tag.label }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="lang-icon">{{ lang.icon }}</div>
|
||||
<div class="lang-name">{{ lang.name }}</div>
|
||||
<div class="lang-metaphor">{{ lang.metaphor }}</div>
|
||||
<div class="lang-description">{{ lang.description }}</div>
|
||||
<div class="lang-stats">
|
||||
<div class="stat">
|
||||
<span class="stat-label">性能</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill performance"
|
||||
:style="{ width: lang.stats.performance + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">开发效率</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill efficiency"
|
||||
:style="{ width: lang.stats.efficiency + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">生态</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill ecosystem"
|
||||
:style="{ width: lang.stats.ecosystem + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">学习曲线</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill learning"
|
||||
:style="{ width: lang.stats.learning + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<Transition name="fade">
|
||||
<div v-if="selectedLang" class="lang-detail">
|
||||
<h3>{{ getLangDetail(selectedLang).title }}</h3>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ getCurrentLang().icon }}</span>
|
||||
<span class="detail-title">{{ getCurrentLang().name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-sections">
|
||||
<div class="detail-section">
|
||||
<h4>核心特点</h4>
|
||||
<h6>🎯 适用场景</h6>
|
||||
<ul>
|
||||
<li
|
||||
v-for="feature in getLangDetail(selectedLang).features"
|
||||
:key="feature"
|
||||
>
|
||||
{{ feature }}
|
||||
<li v-for="scenario in getCurrentLang().scenarios" :key="scenario">
|
||||
{{ scenario }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>典型应用</h4>
|
||||
<h6>✅ 优势</h6>
|
||||
<ul>
|
||||
<li
|
||||
v-for="app in getLangDetail(selectedLang).applications"
|
||||
:key="app"
|
||||
>
|
||||
{{ app }}
|
||||
</li>
|
||||
<li v-for="pro in getCurrentLang().pros" :key="pro">{{ pro }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>优劣势</h4>
|
||||
<div class="pros-cons">
|
||||
<div class="pros">
|
||||
<strong>优势:</strong>
|
||||
<ul>
|
||||
<li
|
||||
v-for="pro in getLangDetail(selectedLang).pros"
|
||||
:key="pro"
|
||||
>
|
||||
{{ pro }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cons">
|
||||
<strong>劣势:</strong>
|
||||
<ul>
|
||||
<li
|
||||
v-for="con in getLangDetail(selectedLang).cons"
|
||||
:key="con"
|
||||
>
|
||||
{{ con }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h6>❌ 劣势</h6>
|
||||
<ul>
|
||||
<li v-for="con in getCurrentLang().cons" :key="con">{{ con }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
|
||||
<div v-if="!selectedLang" class="hint-text">
|
||||
👆 点击上方任意语言,查看详细说明
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>选择语言时,先想清楚"我要解决什么问题",而不是"哪个语言最火"。初创公司选 Python/Node.js 快速验证,大厂选 Java/Go 保证稳定,游戏开发选 C++ 追求极致性能。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedLang = ref('Java')
|
||||
const selectedLang = ref('Go')
|
||||
|
||||
const languages = [
|
||||
{
|
||||
name: 'Java',
|
||||
icon: '☕',
|
||||
year: '1995',
|
||||
tags: [
|
||||
{ type: 'primary', label: '企业级' },
|
||||
{ type: 'success', label: '跨平台' },
|
||||
{ type: 'info', label: '强类型' }
|
||||
name: 'Go',
|
||||
icon: '🐹',
|
||||
metaphor: '电动螺丝刀',
|
||||
description: '云原生时代的高效工具',
|
||||
scenarios: [
|
||||
'微服务架构(Docker、K8s 都是 Go 写的)',
|
||||
'高并发 API 服务',
|
||||
'DevOps 工具开发',
|
||||
'区块链基础设施'
|
||||
],
|
||||
description: '企业级开发的霸主,Spring Boot 生态成熟',
|
||||
stats: { performance: 75, efficiency: 60, ecosystem: 95, learning: 40 }
|
||||
pros: [
|
||||
'并发性能优秀(Goroutine 轻量级协程)',
|
||||
'编译快,部署简单(单一可执行文件)',
|
||||
'语法简洁,学习曲线平缓',
|
||||
'内存占用低,性能接近 C++'
|
||||
],
|
||||
cons: [
|
||||
'生态不如 Java/Python 成熟',
|
||||
'错误处理繁琐(if err != nil)',
|
||||
'泛型支持较弱(Go 1.18+ 引入)',
|
||||
'不适合 CPU 密集型任务'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Python',
|
||||
icon: '🐍',
|
||||
year: '1991',
|
||||
tags: [
|
||||
{ type: 'primary', label: 'AI/ML' },
|
||||
{ type: 'success', label: '简洁' },
|
||||
{ type: 'warning', label: '动态类型' }
|
||||
metaphor: '瑞士军刀',
|
||||
description: '什么都能干的全能工具',
|
||||
scenarios: [
|
||||
'AI/机器学习(PyTorch、TensorFlow)',
|
||||
'数据分析和处理',
|
||||
'快速原型开发',
|
||||
'自动化脚本'
|
||||
],
|
||||
description: 'AI 与数据分析的首选语言',
|
||||
stats: { performance: 30, efficiency: 95, ecosystem: 95, learning: 95 }
|
||||
pros: [
|
||||
'语法极简,学习曲线平缓',
|
||||
'AI 生态无与伦比',
|
||||
'开发速度快,代码量少',
|
||||
'库丰富,几乎任何功能都有现成方案'
|
||||
],
|
||||
cons: [
|
||||
'运行速度慢(比 Go/Java 慢 10-100 倍)',
|
||||
'GIL 限制多线程性能',
|
||||
'打包部署复杂(依赖地狱)',
|
||||
'动态类型,运行时错误多'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Go',
|
||||
icon: '🐹',
|
||||
year: '2009',
|
||||
tags: [
|
||||
{ type: 'primary', label: '云原生' },
|
||||
{ type: 'success', label: '高性能' },
|
||||
{ type: 'info', label: '并发' }
|
||||
],
|
||||
description: 'Google 出品,云原生时代的宠儿',
|
||||
stats: { performance: 90, efficiency: 85, ecosystem: 75, learning: 80 }
|
||||
},
|
||||
{
|
||||
name: 'Node.js',
|
||||
icon: '💚',
|
||||
year: '2009',
|
||||
tags: [
|
||||
{ type: 'primary', label: '全栈' },
|
||||
{ type: 'success', label: 'NPM' },
|
||||
{ type: 'info', label: '异步' }
|
||||
],
|
||||
description: 'JavaScript 运行时,前后端统一',
|
||||
stats: { performance: 70, efficiency: 85, ecosystem: 95, learning: 75 }
|
||||
},
|
||||
{
|
||||
name: 'C#',
|
||||
icon: '💜',
|
||||
year: '2000',
|
||||
tags: [
|
||||
{ type: 'primary', label: '.NET' },
|
||||
{ type: 'success', label: '跨平台' },
|
||||
{ type: 'info', label: 'Unity' }
|
||||
],
|
||||
description: '微软出品,Windows 生态王者',
|
||||
stats: { performance: 80, efficiency: 70, ecosystem: 85, learning: 50 }
|
||||
},
|
||||
{
|
||||
name: 'Rust',
|
||||
icon: '🦀',
|
||||
year: '2010',
|
||||
tags: [
|
||||
{ type: 'primary', label: '系统级' },
|
||||
{ type: 'success', label: '内存安全' },
|
||||
{ type: 'danger', label: '难学' }
|
||||
],
|
||||
description: 'Mozilla 出品,内存安全的系统语言',
|
||||
stats: { performance: 95, efficiency: 40, ecosystem: 70, learning: 20 }
|
||||
},
|
||||
{
|
||||
name: 'C++',
|
||||
icon: '⚡',
|
||||
year: '1985',
|
||||
tags: [
|
||||
{ type: 'primary', label: '高性能' },
|
||||
{ type: 'success', label: '游戏' },
|
||||
{ type: 'danger', label: '复杂' }
|
||||
],
|
||||
description: '高性能计算的基石',
|
||||
stats: { performance: 98, efficiency: 35, ecosystem: 90, learning: 25 }
|
||||
},
|
||||
{
|
||||
name: 'Ruby',
|
||||
icon: '💎',
|
||||
year: '1995',
|
||||
tags: [
|
||||
{ type: 'primary', label: 'Rails' },
|
||||
{ type: 'success', label: '优雅' },
|
||||
{ type: 'warning', label: '慢' }
|
||||
],
|
||||
description: 'Ruby on Rails,快速开发典范',
|
||||
stats: { performance: 25, efficiency: 90, ecosystem: 70, learning: 85 }
|
||||
}
|
||||
]
|
||||
|
||||
const langDetails = {
|
||||
Java: {
|
||||
title: 'Java - 企业级开发的霸主',
|
||||
features: [
|
||||
'JVM (Java Virtual Machine) 实现跨平台',
|
||||
'强类型系统,编译时检查',
|
||||
'Spring 全家桶生态成熟',
|
||||
'JIT 编译器提供接近 C++ 的性能'
|
||||
],
|
||||
applications: [
|
||||
name: 'Java',
|
||||
icon: '☕',
|
||||
metaphor: '重型挖掘机',
|
||||
description: '企业级开发的稳定选择',
|
||||
scenarios: [
|
||||
'大型企业系统(银行、保险、电商)',
|
||||
'Android 应用开发',
|
||||
'大数据处理(Hadoop、Spark)',
|
||||
'微服务架构(Spring Cloud)'
|
||||
],
|
||||
pros: [
|
||||
'✅ 生态极其成熟,框架完备',
|
||||
'✅ 强类型,编译时检查',
|
||||
'✅ 多线程模型成熟',
|
||||
'✅ 跨平台,JVM 优化强大'
|
||||
'生态极其成熟,框架完备',
|
||||
'强类型,编译时检查',
|
||||
'多线程模型成熟',
|
||||
'跨平台,JVM 优化强大'
|
||||
],
|
||||
cons: [
|
||||
'❌ 代码冗长,样板代码多',
|
||||
'❌ 启动慢,内存占用高',
|
||||
'❌ 学习曲线陡峭(Spring 全家桶)',
|
||||
'❌ 版本更新快,兼容性问题'
|
||||
'代码冗长,样板代码多',
|
||||
'启动慢,内存占用高',
|
||||
'学习曲线陡峭(Spring 全家桶)',
|
||||
'版本更新快,兼容性问题'
|
||||
]
|
||||
},
|
||||
Python: {
|
||||
title: 'Python - AI 与脚本之王',
|
||||
features: [
|
||||
'极简语法,像读英语一样',
|
||||
'AI 生态无与伦比(NumPy、PyTorch)',
|
||||
'快速开发,代码量少',
|
||||
'丰富的科学计算库'
|
||||
],
|
||||
applications: [
|
||||
'AI/机器学习(所有主流框架)',
|
||||
'数据分析(Pandas、Jupyter)',
|
||||
'脚本自动化(运维、数据处理)',
|
||||
'Web 开发(Django、Flask)'
|
||||
],
|
||||
pros: [
|
||||
'✅ 语法简单,学习曲线平缓',
|
||||
'✅ AI 生态无与伦比',
|
||||
'✅ 快速开发,代码量少',
|
||||
'✅ 社区活跃,库丰富'
|
||||
],
|
||||
cons: [
|
||||
'❌ 运行速度慢(比 Java/Go 慢 10-100 倍)',
|
||||
'❌ 动态类型,运行时错误多',
|
||||
'❌ GIL 限制,多线程性能差',
|
||||
'❌ 打包部署复杂(依赖地狱)'
|
||||
]
|
||||
},
|
||||
Go: {
|
||||
title: 'Go - 云原生时代的宠儿',
|
||||
features: [
|
||||
'Goroutine (协程) 轻量级并发',
|
||||
'简洁语法,25 个关键字',
|
||||
'快速编译,比 Java 快 10 倍',
|
||||
'单一可执行文件,无依赖'
|
||||
],
|
||||
applications: [
|
||||
'云原生基础设施(Docker、K8s)',
|
||||
'微服务架构',
|
||||
'DevOps 工具(Terraform、Prometheus)',
|
||||
'区块链(Hyperledger Fabric)'
|
||||
],
|
||||
pros: [
|
||||
'✅ 原生并发,性能接近 C++',
|
||||
'✅ 简洁语法,学习曲线平缓',
|
||||
'✅ 编译快,部署简单',
|
||||
'✅ 单一可执行文件,无依赖'
|
||||
],
|
||||
cons: [
|
||||
'❌ 生态不如 Java/Python 成熟',
|
||||
'❌ 错误处理繁琐(if err != nil)',
|
||||
'❌ 泛型支持较弱(Go 1.18+ 才引入)',
|
||||
'❌ 不如 Java/Python 灵活'
|
||||
]
|
||||
},
|
||||
'Node.js': {
|
||||
title: 'Node.js - 全栈工程师的利器',
|
||||
features: [
|
||||
'事件驱动,非阻塞 I/O',
|
||||
'前后端统一语言',
|
||||
'NPM 世界最大的包仓库',
|
||||
'适合实时应用'
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
name: 'Node.js',
|
||||
icon: '💚',
|
||||
metaphor: '万能扳手',
|
||||
description: '前后端统一的利器',
|
||||
scenarios: [
|
||||
'全栈 Web 应用(React + Node.js)',
|
||||
'实时系统(聊天应用、协作工具)',
|
||||
'Serverless(AWS Lambda、Vercel)',
|
||||
'CLI 工具(VS Code、Webpack)'
|
||||
'I/O 密集型 API'
|
||||
],
|
||||
pros: [
|
||||
'✅ 前后端统一,减少语言切换成本',
|
||||
'✅ NPM 生态庞大,库丰富',
|
||||
'✅ 适合 I/O 密集型应用',
|
||||
'✅ 社区活跃,更新快'
|
||||
'前后端统一语言,减少切换成本',
|
||||
'NPM 生态庞大,世界最大包仓库',
|
||||
'适合 I/O 密集型应用',
|
||||
'事件驱动,非阻塞 I/O'
|
||||
],
|
||||
cons: [
|
||||
'❌ 单线程,CPU 密集型任务性能差',
|
||||
'❌ 回调地狱(async/await 有改善)',
|
||||
'❌ 动态类型,运行时错误多',
|
||||
'❌ 版本兼容性问题多'
|
||||
'单线程,CPU 密集型性能差',
|
||||
'回调地狱(虽然 async/await 有改善)',
|
||||
'动态类型,运行时错误多',
|
||||
'版本兼容性问题多'
|
||||
]
|
||||
},
|
||||
'C#': {
|
||||
title: 'C# - Windows 生态的王者',
|
||||
features: [
|
||||
'微软 Visual Studio 极其强大',
|
||||
'.NET Core 实现跨平台',
|
||||
'高性能,CoreFX 优化',
|
||||
'Unity 游戏开发官方语言'
|
||||
],
|
||||
applications: [
|
||||
'Windows 应用开发',
|
||||
'游戏开发(Unity 引擎)',
|
||||
'Web 开发(ASP.NET Core)',
|
||||
'Azure 云服务'
|
||||
],
|
||||
pros: [
|
||||
'✅ Visual Studio 极其强大',
|
||||
'✅ ASP.NET Core 性能优秀',
|
||||
'✅ 跨平台(.NET Core)',
|
||||
'✅ 游戏开发(Unity)'
|
||||
],
|
||||
cons: [
|
||||
'❌ Windows 历史包袱重',
|
||||
'❌ 社区不如 Java/Python 活跃',
|
||||
'❌ 学习曲线陡峭',
|
||||
'❌ 开源生态相对较弱'
|
||||
]
|
||||
},
|
||||
Rust: {
|
||||
title: 'Rust - 系统级编程的未来',
|
||||
features: [
|
||||
'所有权系统保证内存安全',
|
||||
'零成本抽象',
|
||||
'编译时保证无内存泄漏',
|
||||
'WebAssembly 支持'
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
name: 'Rust',
|
||||
icon: '🦀',
|
||||
metaphor: '激光切割机',
|
||||
description: '内存安全的系统级工具',
|
||||
scenarios: [
|
||||
'系统编程(操作系统、数据库)',
|
||||
'区块链(Solana、Polkadot)',
|
||||
'WebAssembly(前端高性能计算)',
|
||||
'基础设施(AWS Firecracker)'
|
||||
],
|
||||
pros: [
|
||||
'✅ 内存安全,无 GC',
|
||||
'✅ 性能接近 C++',
|
||||
'✅ 现代化语法',
|
||||
'✅ WebAssembly 支持'
|
||||
'内存安全,编译时保证无泄漏',
|
||||
'性能接近 C++',
|
||||
'现代化语法,零成本抽象',
|
||||
'无 GC,运行时开销低'
|
||||
],
|
||||
cons: [
|
||||
'❌ 学习曲线极其陡峭',
|
||||
'❌ 编译时间长',
|
||||
'❌ 生态不如 Go/Rust 成熟',
|
||||
'❌ 开发速度慢'
|
||||
'学习曲线极其陡峭',
|
||||
'编译时间长',
|
||||
'生态不如 Go/Java 成熟',
|
||||
'开发速度慢'
|
||||
]
|
||||
},
|
||||
'C++': {
|
||||
title: 'C++ - 高性能计算的基石',
|
||||
features: [
|
||||
'极致性能,无语言能超越',
|
||||
'底层控制,直接操作内存',
|
||||
'游戏引擎标准',
|
||||
'现代 C++ (11/14/17/20)'
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
name: 'C++',
|
||||
icon: '⚡',
|
||||
metaphor: '工业电钻',
|
||||
description: '高性能计算的基石',
|
||||
scenarios: [
|
||||
'游戏开发(Unreal Engine)',
|
||||
'高频交易(金融系统)',
|
||||
'浏览器引擎(Chrome V8)',
|
||||
'AI 框架底层(PyTorch、TF)'
|
||||
],
|
||||
pros: ['✅ 性能极致', '✅ 底层控制力强', '✅ 游戏开发标准', '✅ 生态成熟'],
|
||||
cons: [
|
||||
'❌ 学习曲线极其陡峭',
|
||||
'❌ 内存管理复杂(易泄漏)',
|
||||
'❌ 开发效率低',
|
||||
'❌ 不适合 Web 开发'
|
||||
]
|
||||
},
|
||||
Ruby: {
|
||||
title: 'Ruby - 快速开发的典范',
|
||||
features: ['Ruby on Rails 框架', '约定优于配置', '代码优雅', '快速开发'],
|
||||
applications: [
|
||||
'初创公司(GitHub、Airbnb)',
|
||||
'快速原型(MVP、黑客松)',
|
||||
'Web 开发(Rails、Sinatra)',
|
||||
'CI/CD 脚本'
|
||||
],
|
||||
pros: [
|
||||
'✅ Rails 框架极其成熟',
|
||||
'✅ 快速开发,代码优雅',
|
||||
'✅ 约定优于配置',
|
||||
'✅ 社区活跃'
|
||||
'性能极致,无语言能超越',
|
||||
'底层控制力强,直接操作内存',
|
||||
'游戏开发标准',
|
||||
'生态成熟'
|
||||
],
|
||||
cons: [
|
||||
'❌ 性能较差',
|
||||
'❌ 动态类型,运行时错误多',
|
||||
'❌ 多线程性能差',
|
||||
'❌ 生态不如 Java/Python 广泛'
|
||||
'学习曲线极其陡峭',
|
||||
'内存管理复杂(易泄漏)',
|
||||
'开发效率低',
|
||||
'不适合 Web 开发'
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const getLangDetail = (name) => {
|
||||
return langDetails[name] || {}
|
||||
const getCurrentLang = () => {
|
||||
return languages.find(l => l.name === selectedLang.value) || languages[0]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.backend-languages-demo {
|
||||
border-radius: 16px;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
overflow: hidden;
|
||||
margin: 2rem 0;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.language-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.language-card {
|
||||
padding: 1rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.language-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.language-card.active {
|
||||
@@ -479,150 +305,72 @@ const getLangDetail = (name) => {
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.lang-header {
|
||||
.lang-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.lang-name {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.lang-metaphor {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.lang-description {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-3);
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.lang-detail {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-top: 0.75rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.lang-icon {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lang-header h4 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.lang-year {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.lang-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tag-primary {
|
||||
background: #e0f2fe;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
.tag-success {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.tag-info {
|
||||
background: #e0e7ff;
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.tag-warning {
|
||||
background: #fef3c7;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.tag-danger {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.lang-description {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.lang-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
min-width: 60px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.stat-fill.performance {
|
||||
background: linear-gradient(90deg, #fbbf24, #f59e0b);
|
||||
}
|
||||
|
||||
.stat-fill.efficiency {
|
||||
background: linear-gradient(90deg, #34d399, #10b981);
|
||||
}
|
||||
|
||||
.stat-fill.ecosystem {
|
||||
background: linear-gradient(90deg, #60a5fa, #3b82f6);
|
||||
}
|
||||
|
||||
.stat-fill.learning {
|
||||
background: linear-gradient(90deg, #f472b6, #ec4899);
|
||||
}
|
||||
|
||||
.lang-detail {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
animation: fade-in 0.3s ease;
|
||||
}
|
||||
|
||||
.lang-detail h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--vp-c-brand);
|
||||
.detail-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
.detail-title {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.detail-section h4 {
|
||||
.detail-sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.detail-section h6 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.detail-section ul {
|
||||
@@ -632,12 +380,12 @@ const getLangDetail = (name) => {
|
||||
}
|
||||
|
||||
.detail-section li {
|
||||
padding: 0.5rem 0;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
padding-left: 1.5rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.detail-section li::before {
|
||||
@@ -648,43 +396,27 @@ const getLangDetail = (name) => {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pros-cons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.pros,
|
||||
.cons {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.pros strong {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.cons strong {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+172
-580
@@ -1,11 +1,16 @@
|
||||
<template>
|
||||
<div class="concurrency-model-demo">
|
||||
<div class="demo-header">
|
||||
<h4>并发模型对比</h4>
|
||||
<p class="subtitle">不同语言处理并发请求的方式</p>
|
||||
<span class="icon">🔄</span>
|
||||
<span class="title">并发模型</span>
|
||||
<span class="subtitle">不同语言处理多任务的方式</span>
|
||||
</div>
|
||||
|
||||
<div class="model-grid">
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">餐厅工作</span>:有的餐厅多个服务员同时服务(多线程),有的只有一个服务员但动作极快(事件循环),有的像流水线一样分工协作(协程)。
|
||||
</div>
|
||||
|
||||
<div class="models-grid">
|
||||
<div
|
||||
v-for="model in models"
|
||||
:key="model.name"
|
||||
@@ -14,84 +19,63 @@
|
||||
@click="selectedModel = model.name"
|
||||
>
|
||||
<div class="model-icon">{{ model.icon }}</div>
|
||||
<h5>{{ model.name }}</h5>
|
||||
<div class="model-tag">{{ model.tag }}</div>
|
||||
<div class="model-name">{{ model.name }}</div>
|
||||
<div class="model-lang">{{ model.language }}</div>
|
||||
<div class="model-desc">{{ model.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="fade" mode="out-in">
|
||||
<div :key="selectedModel" class="model-detail">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div v-if="selectedModel" :key="selectedModel" class="model-detail">
|
||||
<div class="detail-header">
|
||||
<h5>{{ getModelDetail(selectedModel).title }}</h5>
|
||||
<div class="model-stats">
|
||||
<div class="stat">
|
||||
<span class="stat-label">并发能力</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill"
|
||||
:style="{
|
||||
width: getModelDetail(selectedModel).concurrency + '%'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<h6>{{ getModelInfo().title }}</h6>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">并发能力</span>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" :style="{ width: getModelInfo().concurrency + '%' }"></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">内存开销</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill memory"
|
||||
:style="{ width: getModelDetail(selectedModel).memory + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">CPU 利用率</span>
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-fill cpu"
|
||||
:style="{ width: getModelDetail(selectedModel).cpu + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">内存开销</span>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill memory" :style="{ width: getModelInfo().memory + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-example">
|
||||
<h6>代码示例</h6>
|
||||
<pre><code>{{ getModelDetail(selectedModel).code }}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="visualization">
|
||||
<h6>并发可视化</h6>
|
||||
<ConcurrencyVisualization :model="selectedModel" />
|
||||
<code>{{ getModelInfo().code }}</code>
|
||||
</div>
|
||||
|
||||
<div class="pros-cons">
|
||||
<div class="pros">
|
||||
<h6>优势</h6>
|
||||
<strong>✅ 优势</strong>
|
||||
<ul>
|
||||
<li v-for="pro in getModelDetail(selectedModel).pros" :key="pro">
|
||||
{{ pro }}
|
||||
</li>
|
||||
<li v-for="pro in getModelInfo().pros" :key="pro">{{ pro }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cons">
|
||||
<h6>劣势</h6>
|
||||
<strong>❌ 劣势</strong>
|
||||
<ul>
|
||||
<li v-for="con in getModelDetail(selectedModel).cons" :key="con">
|
||||
{{ con }}
|
||||
</li>
|
||||
<li v-for="con in getModelInfo().cons" :key="con">{{ con }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>Go 的协程适合高并发 I/O,Java 的线程池适合稳定的企业级应用,Node.js 的事件循环适合简单的 I/O 密集型任务。根据场景选择,而不是盲目追求"并发数"。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineComponent } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const selectedModel = ref('Goroutine')
|
||||
|
||||
@@ -99,624 +83,244 @@ const models = [
|
||||
{
|
||||
name: 'Goroutine',
|
||||
icon: '🐹',
|
||||
tag: 'Go',
|
||||
description: '轻量级协程,百万级并发'
|
||||
language: 'Go',
|
||||
description: '轻量级协程'
|
||||
},
|
||||
{
|
||||
name: 'Thread',
|
||||
name: 'Thread Pool',
|
||||
icon: '🧵',
|
||||
tag: 'Java',
|
||||
description: '传统线程池,成熟稳定'
|
||||
language: 'Java',
|
||||
description: '线程池'
|
||||
},
|
||||
{
|
||||
name: 'Async/Await',
|
||||
name: 'Event Loop',
|
||||
icon: '⚡',
|
||||
tag: 'Node.js',
|
||||
description: '事件循环,非阻塞 I/O'
|
||||
language: 'Node.js',
|
||||
description: '事件循环'
|
||||
},
|
||||
{
|
||||
name: 'Async/Await',
|
||||
icon: '🦀',
|
||||
tag: 'Rust',
|
||||
description: '零成本抽象,高性能'
|
||||
language: 'Rust',
|
||||
description: '异步运行时'
|
||||
}
|
||||
]
|
||||
|
||||
const modelDetails = {
|
||||
const modelInfo = {
|
||||
Goroutine: {
|
||||
title: 'Go Goroutine (协程)',
|
||||
concurrency: 95,
|
||||
memory: 90,
|
||||
cpu: 85,
|
||||
code: `// Go: 启动 10 万个协程
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func task(id int) {
|
||||
fmt.Printf("Task %d\\n", id)
|
||||
}
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 100000; i++ {
|
||||
go task(i) // 启动协程
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}`,
|
||||
pros: [
|
||||
'✅ 轻量级:每个协程仅 2KB 栈内存',
|
||||
'✅ 可创建百万级协程',
|
||||
'✅ 语法简洁(go 关键字)',
|
||||
'✅ 通信顺序进程(CSP)模型'
|
||||
],
|
||||
cons: [
|
||||
'❌ 需要手动管理协程生命周期',
|
||||
'❌ 错误处理繁琐(if err != nil)',
|
||||
'❌ 不如线程模型成熟'
|
||||
]
|
||||
code: 'go func() { /* 任务 */ }()',
|
||||
pros: ['轻量级(2KB 栈内存)', '可创建百万级协程', '语法简洁'],
|
||||
cons: ['需要手动管理生命周期', '错误处理繁琐']
|
||||
},
|
||||
Thread: {
|
||||
title: 'Java Thread (线程池)',
|
||||
'Thread Pool': {
|
||||
title: 'Java Thread Pool (线程池)',
|
||||
concurrency: 70,
|
||||
memory: 40,
|
||||
cpu: 80,
|
||||
code: `// Java: 线程池处理并发
|
||||
import java.util.concurrent.*;
|
||||
|
||||
ExecutorService executor =
|
||||
Executors.newFixedThreadPool(10);
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
final int taskId = i;
|
||||
executor.submit(() -> {
|
||||
System.out.println("Task " + taskId);
|
||||
});
|
||||
}
|
||||
|
||||
executor.shutdown();`,
|
||||
pros: [
|
||||
'✅ 成熟稳定,企业级应用首选',
|
||||
'✅ 异常处理机制完善',
|
||||
'✅ 调试工具丰富(JConsole、VisualVM)',
|
||||
'✅ 线程池优化(ExecutorService)'
|
||||
],
|
||||
cons: [
|
||||
'❌ 线程重(1-2MB 栈内存)',
|
||||
'❌ 上下文切换开销大',
|
||||
'❌ 并发数受限(通常 < 10000)',
|
||||
'❌ 锁竞争复杂'
|
||||
]
|
||||
code: 'executor.submit(() -> { /* 任务 */ });',
|
||||
pros: ['成熟稳定', '异常处理完善', '工具丰富'],
|
||||
cons: ['线程重(1-2MB 栈)', '上下文切换开销大']
|
||||
},
|
||||
'Async/Await': {
|
||||
title: 'Node.js Async/Await (事件循环)',
|
||||
'Event Loop': {
|
||||
title: 'Node.js Event Loop (事件循环)',
|
||||
concurrency: 85,
|
||||
memory: 75,
|
||||
cpu: 60,
|
||||
code: `// Node.js: 异步处理
|
||||
const axios = require('axios');
|
||||
|
||||
async function fetch(id) {
|
||||
const response = await axios.get(url);
|
||||
console.log(\`Task \${id}\`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const tasks = [];
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
tasks.push(fetch(i));
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
main();`,
|
||||
pros: [
|
||||
'✅ 适合 I/O 密集型应用',
|
||||
'✅ 单线程,无锁竞争',
|
||||
'✅ 事件驱动,非阻塞',
|
||||
'✅ 语法优雅(async/await)'
|
||||
],
|
||||
cons: [
|
||||
'❌ 单线程,CPU 密集型性能差',
|
||||
'❌ 回调地狱(虽然 async/await 有改善)',
|
||||
'❌ 无法利用多核 CPU(需要 Worker Threads)',
|
||||
'❌ 错误堆栈复杂'
|
||||
]
|
||||
code: 'async function task() { /* 任务 */ }',
|
||||
pros: ['适合 I/O 密集型', '单线程无锁竞争', '语法优雅'],
|
||||
cons: ['CPU 密集型性能差', '无法利用多核']
|
||||
},
|
||||
RustAsync: {
|
||||
'Async/Await': {
|
||||
title: 'Rust Async/Await (零成本抽象)',
|
||||
concurrency: 90,
|
||||
memory: 95,
|
||||
cpu: 90,
|
||||
code: `// Rust: 异步运行时(tokio)
|
||||
use tokio::task;
|
||||
|
||||
async fn task(id: u32) {
|
||||
println!("Task {}", id);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut handles = vec![];
|
||||
|
||||
for i in 0..100_000 {
|
||||
let handle = task::spawn(async move {
|
||||
task(i).await;
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
}`,
|
||||
pros: [
|
||||
'✅ 零成本抽象(编译成状态机)',
|
||||
'✅ 内存安全(编译时保证)',
|
||||
'✅ 性能接近手动管理',
|
||||
'✅ 无 GC,无运行时开销'
|
||||
],
|
||||
cons: [
|
||||
'❌ 学习曲线极其陡峭',
|
||||
'❌ 需要运行时(tokio、async-std)',
|
||||
'❌ 编译时间长',
|
||||
'❌ 生态不如 Go/Node.js 成熟'
|
||||
]
|
||||
code: 'task::spawn(async move { /* 任务 */ });',
|
||||
pros: ['零成本抽象', '内存安全', '性能接近手动管理'],
|
||||
cons: ['学习曲线陡峭', '需要运行时']
|
||||
}
|
||||
}
|
||||
|
||||
const getModelDetail = (model) => {
|
||||
if (model === 'Async/Await') {
|
||||
return selectedModel.value === 'Node.js'
|
||||
? modelDetails['Async/Await']
|
||||
: modelDetails.RustAsync
|
||||
}
|
||||
return modelDetails[model] || modelDetails.Goroutine
|
||||
}
|
||||
|
||||
// ConcurrencyVisualization component
|
||||
const ConcurrencyVisualization = defineComponent({
|
||||
name: 'ConcurrencyVisualization',
|
||||
props: {
|
||||
model: String
|
||||
},
|
||||
template: `
|
||||
<div class="concurrency-viz">
|
||||
<div class="viz-container">
|
||||
<div class="task-queue">
|
||||
<div class="queue-label">任务队列</div>
|
||||
<div class="queue-items">
|
||||
<div
|
||||
v-for="i in 20"
|
||||
:key="i"
|
||||
class="queue-item"
|
||||
:class="{ processing: i <= activeWorkers }"
|
||||
>
|
||||
Task {{ i }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workers">
|
||||
<div class="workers-label">{{ workersLabel }}</div>
|
||||
<div class="worker-pool">
|
||||
<div
|
||||
v-for="i in maxWorkers"
|
||||
:key="i"
|
||||
class="worker"
|
||||
:class="{ active: i <= activeWorkers }"
|
||||
>
|
||||
{{ i <= activeWorkers ? '⚡' : '💤' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
setup(props) {
|
||||
const workersLabel = computed(() => {
|
||||
const labels = {
|
||||
Goroutine: 'Goroutines (可无限创建)',
|
||||
Thread: 'Thread Pool (固定数量)',
|
||||
'Async/Await': 'Event Loop (单线程)',
|
||||
RustAsync: 'Async Tasks (可无限创建)'
|
||||
}
|
||||
return labels[props.model] || 'Workers'
|
||||
})
|
||||
|
||||
const maxWorkers = computed(() => {
|
||||
const counts = {
|
||||
Goroutine: 100,
|
||||
Thread: 10,
|
||||
'Async/Await': 1,
|
||||
RustAsync: 100
|
||||
}
|
||||
return counts[props.model] || 10
|
||||
})
|
||||
|
||||
const activeWorkers = computed(() => {
|
||||
const actives = {
|
||||
Goroutine: 100,
|
||||
Thread: 10,
|
||||
'Async/Await': 1,
|
||||
RustAsync: 100
|
||||
}
|
||||
return actives[props.model] || 10
|
||||
})
|
||||
|
||||
return {
|
||||
workersLabel,
|
||||
maxWorkers,
|
||||
activeWorkers
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { computed, defineComponent } from 'vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ConcurrencyVisualization: defineComponent({
|
||||
name: 'ConcurrencyVisualization',
|
||||
props: {
|
||||
model: String
|
||||
},
|
||||
template: `
|
||||
<div class="concurrency-viz">
|
||||
<div class="viz-container">
|
||||
<div class="task-queue">
|
||||
<div class="queue-label">任务队列</div>
|
||||
<div class="queue-items">
|
||||
<div
|
||||
v-for="i in 20"
|
||||
:key="i"
|
||||
class="queue-item"
|
||||
:class="{ processing: i <= activeWorkers }"
|
||||
>
|
||||
Task {{ i }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workers">
|
||||
<div class="workers-label">{{ workersLabel }}</div>
|
||||
<div class="worker-pool">
|
||||
<div
|
||||
v-for="i in displayWorkers"
|
||||
:key="i"
|
||||
class="worker"
|
||||
:class="{ active: i <= activeWorkers }"
|
||||
>
|
||||
{{ i <= activeWorkers ? '⚡' : '💤' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
setup(props) {
|
||||
const workersLabel = computed(() => {
|
||||
const labels = {
|
||||
Goroutine: 'Goroutines (可无限创建)',
|
||||
Thread: 'Thread Pool (固定数量)',
|
||||
'Async/Await': 'Event Loop (单线程)',
|
||||
RustAsync: 'Async Tasks (可无限创建)'
|
||||
}
|
||||
return labels[props.model] || 'Workers'
|
||||
})
|
||||
|
||||
const maxWorkers = computed(() => {
|
||||
const counts = {
|
||||
Goroutine: 20,
|
||||
Thread: 10,
|
||||
'Async/Await': 1,
|
||||
RustAsync: 20
|
||||
}
|
||||
return counts[props.model] || 10
|
||||
})
|
||||
|
||||
const activeWorkers = computed(() => {
|
||||
const actives = {
|
||||
Goroutine: 20,
|
||||
Thread: 10,
|
||||
'Async/Await': 1,
|
||||
RustAsync: 20
|
||||
}
|
||||
return actives[props.model] || 10
|
||||
})
|
||||
|
||||
const displayWorkers = computed(() => {
|
||||
// 限制显示数量,避免 DOM 过多
|
||||
return Math.min(maxWorkers.value, 20)
|
||||
})
|
||||
|
||||
return {
|
||||
workersLabel,
|
||||
maxWorkers,
|
||||
activeWorkers,
|
||||
displayWorkers
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const getModelInfo = () => {
|
||||
return modelInfo[selectedModel.value] || modelInfo.Goroutine
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.concurrency-model-demo {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.model-grid {
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.models-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
background: var(--vp-c-bg-soft);
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.model-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.model-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.model-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.model-card h5 {
|
||||
margin: 0.5rem 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.model-tag {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
.model-name {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.model-lang {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.model-desc {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.model-detail {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
animation: fade-in 0.3s ease;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 2rem;
|
||||
.detail-header h6 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.detail-header h5 {
|
||||
margin: 0 0 1.5rem 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.model-stats {
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.stat-bar {
|
||||
height: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
height: 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), #8b5cf6);
|
||||
background: var(--vp-c-brand);
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.stat-fill.memory {
|
||||
background: linear-gradient(90deg, #f59e0b, #d97706);
|
||||
}
|
||||
|
||||
.stat-fill.cpu {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
background: var(--vp-c-green-1);
|
||||
}
|
||||
|
||||
.code-example {
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-example h6 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #4ec9b0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.code-example pre {
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.code-example code {
|
||||
color: #4ec9b0;
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.6;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.visualization {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.visualization h6 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.concurrency-viz {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.viz-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.task-queue,
|
||||
.workers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.queue-label,
|
||||
.workers-label {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.queue-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.queue-item {
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.queue-item.processing {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.worker-pool {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.worker {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
font-size: 1.2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.worker.active {
|
||||
background: #dcfce7;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.pros-cons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.pros h6,
|
||||
.cons h6 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1rem;
|
||||
.pros strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-green-1);
|
||||
}
|
||||
|
||||
.pros h6 {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.cons h6 {
|
||||
color: #ef4444;
|
||||
.cons strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-red-1);
|
||||
}
|
||||
|
||||
.pros ul,
|
||||
@@ -728,15 +332,15 @@ export default {
|
||||
|
||||
.pros li,
|
||||
.cons li {
|
||||
padding: 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.15rem 0;
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
@@ -744,28 +348,16 @@ export default {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.model-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.viz-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.pros-cons {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+127
-439
@@ -1,144 +1,50 @@
|
||||
<template>
|
||||
<div class="developer-efficiency-demo">
|
||||
<div class="demo-header">
|
||||
<h4>开发效率对比</h4>
|
||||
<p class="subtitle">不同语言完成相同任务所需的代码量和时间</p>
|
||||
<span class="icon">⏱️</span>
|
||||
<span class="title">开发效率</span>
|
||||
<span class="subtitle">不同语言完成相同任务的时间成本</span>
|
||||
</div>
|
||||
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">装修房子</span>:有的装修队能快速完工但质量一般(Python、Ruby),有的慢工出细活(Rust、C++),有的速度和质量都不错(Go、Node.js)。
|
||||
</div>
|
||||
|
||||
<div class="task-selector">
|
||||
<label>选择任务:</label>
|
||||
<select v-model="selectedTask" @change="updateMetrics">
|
||||
<option v-for="task in tasks" :key="task.id" :value="task.id">
|
||||
{{ task.name }}
|
||||
</option>
|
||||
<select v-model="selectedTask">
|
||||
<option value="rest">REST API</option>
|
||||
<option value="web">Web 应用</option>
|
||||
<option value="script">数据处理脚本</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-icon">📝</span>
|
||||
<h5>代码行数</h5>
|
||||
</div>
|
||||
<div class="metric-chart">
|
||||
<div
|
||||
v-for="lang in sortedLanguages"
|
||||
:key="lang.name"
|
||||
class="metric-bar"
|
||||
>
|
||||
<div class="bar-label">{{ lang.name }}</div>
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: getBarWidth(lang.lines) + '%' }"
|
||||
>
|
||||
<span class="bar-value">{{ lang.lines }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-insight">
|
||||
<p>
|
||||
<strong>💡 分析:</strong>
|
||||
{{ getTaskDetail(selectedTask).linesInsight }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="efficiency-chart">
|
||||
<div class="chart-header">
|
||||
<span>开发时间(小时)</span>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-icon">⏱️</span>
|
||||
<h5>开发时间(小时)</h5>
|
||||
</div>
|
||||
<div class="metric-chart">
|
||||
<div
|
||||
v-for="lang in sortedLanguages"
|
||||
:key="lang.name"
|
||||
class="metric-bar"
|
||||
>
|
||||
<div class="bar-label">{{ lang.name }}</div>
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill time"
|
||||
:style="{ width: getBarWidth(lang.time) + '%' }"
|
||||
>
|
||||
<span class="bar-value">{{ lang.time }}h</span>
|
||||
</div>
|
||||
<div class="bars">
|
||||
<div
|
||||
v-for="lang in sortedLanguages"
|
||||
:key="lang.name"
|
||||
class="bar-wrapper"
|
||||
>
|
||||
<div class="bar-label">{{ lang.name }}</div>
|
||||
<div class="bar-track">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: (lang.time / maxTime * 100) + '%' }"
|
||||
>
|
||||
<span class="bar-value">{{ lang.time }}h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-insight">
|
||||
<p>
|
||||
<strong>💡 分析:</strong>
|
||||
{{ getTaskDetail(selectedTask).timeInsight }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-icon">🐛</span>
|
||||
<h5>调试时间(小时)</h5>
|
||||
</div>
|
||||
<div class="metric-chart">
|
||||
<div
|
||||
v-for="lang in sortedLanguages"
|
||||
:key="lang.name"
|
||||
class="metric-bar"
|
||||
>
|
||||
<div class="bar-label">{{ lang.name }}</div>
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill debug"
|
||||
:style="{ width: getBarWidth(lang.debug) + '%' }"
|
||||
>
|
||||
<span class="bar-value">{{ lang.debug }}h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-insight">
|
||||
<p>
|
||||
<strong>💡 分析:</strong>
|
||||
{{ getTaskDetail(selectedTask).debugInsight }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="efficiency-radar">
|
||||
<h5>综合效率雷达图</h5>
|
||||
<div class="radar-chart">
|
||||
<div class="radar-grid">
|
||||
<div
|
||||
v-for="lang in languages"
|
||||
:key="lang.name"
|
||||
class="radar-point"
|
||||
:style="getRadarPosition(lang)"
|
||||
>
|
||||
<div class="point-label">{{ lang.name }}</div>
|
||||
<div class="point-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="radar-legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot"></span>
|
||||
<span>左上:代码少,开发快</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot"></span>
|
||||
<span>右上:调试快,类型安全</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot"></span>
|
||||
<span>左下:生态好,库丰富</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot"></span>
|
||||
<span>右下:学习简单,上手快</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>初创公司选 Python/Ruby 快速验证想法,大厂选 Java/Go 平衡速度和质量。开发效率不只是写代码的速度,还包括调试、测试、维护的时间成本。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -148,259 +54,153 @@ import { ref, computed } from 'vue'
|
||||
|
||||
const selectedTask = ref('rest')
|
||||
|
||||
const tasks = [
|
||||
{ id: 'rest', name: 'REST API 服务' },
|
||||
{ id: 'web', name: 'Web 应用' },
|
||||
{ id: 'script', name: '数据处理脚本' },
|
||||
{ id: 'micro', name: '微服务' }
|
||||
]
|
||||
|
||||
const languages = [
|
||||
'Python',
|
||||
'Ruby',
|
||||
'Go',
|
||||
'Node.js',
|
||||
'Java',
|
||||
'C#',
|
||||
'Rust',
|
||||
'C++'
|
||||
]
|
||||
|
||||
const taskMetrics = {
|
||||
rest: {
|
||||
Python: { lines: 50, time: 4, debug: 2 },
|
||||
Ruby: { lines: 45, time: 3.5, debug: 2.5 },
|
||||
Go: { lines: 80, time: 5, debug: 1.5 },
|
||||
'Node.js': { lines: 60, time: 4.5, debug: 2 },
|
||||
Java: { lines: 150, time: 8, debug: 2 },
|
||||
'C#': { lines: 120, time: 7, debug: 2 },
|
||||
Rust: { lines: 100, time: 10, debug: 3 },
|
||||
'C++': { lines: 180, time: 12, debug: 5 },
|
||||
linesInsight:
|
||||
'Python 和 Ruby 用最少的代码实现 REST API,得益于简洁的语法和强大的框架(Flask、Sinatra)。Go 虽然语法简洁,但需要显式类型声明。Java 和 C# 的样板代码最多。',
|
||||
timeInsight:
|
||||
'Ruby 和 Python 开发最快,适合快速迭代。Go 和 Node.js 居中,平衡了开发速度和性能。Java 和 C# 开发时间较长,但后期维护成本低。Rust 和 C++ 开发时间最长,主要受学习曲线和编译时间影响。',
|
||||
debugInsight:
|
||||
'Go、Java、C# 的静态类型让调试更容易,大部分错误在编译时就能发现。Python 和 Ruby 虽然开发快,但运行时错误多,调试时间长。Rust 的借用检查器虽然学习曲线陡峭,但能提前发现大量 bug。'
|
||||
},
|
||||
web: {
|
||||
Python: { lines: 200, time: 10, debug: 5 },
|
||||
Ruby: { lines: 180, time: 9, debug: 5 },
|
||||
Go: { lines: 300, time: 12, debug: 4 },
|
||||
'Node.js': { lines: 250, time: 11, debug: 5 },
|
||||
Java: { lines: 500, time: 20, debug: 6 },
|
||||
'C#': { lines: 400, time: 18, debug: 6 },
|
||||
Rust: { lines: 350, time: 25, debug: 8 },
|
||||
'C++': { lines: 600, time: 30, debug: 12 },
|
||||
linesInsight:
|
||||
'Rails 和 Django 的"约定优于配置"让 Web 开发极其高效,代码量最少。全栈的 Node.js 也表现不错。Go 需要 more boilerplate。Java 的 Spring Boot 虽然强大,但配置和样板代码较多。',
|
||||
timeInsight:
|
||||
'Ruby (Rails) 和 Python (Django) 是 Web 开发的效率之王,内置 ORM、模板引擎、认证等功能,开箱即用。Node.js 的全栈特性让前后端统一,减少沟通成本。Go 和 Java 需要更多配置和 boilerplate。',
|
||||
debugInsight:
|
||||
'静态类型语言(Go、Java、C#)在大型 Web 项目中优势明显,IDE 支持更好,重构更安全。Python 和 Ruby 在小项目中调试很快,但随着项目增长,动态类型带来的维护成本会急剧上升。'
|
||||
},
|
||||
script: {
|
||||
Python: { lines: 20, time: 1, debug: 0.5 },
|
||||
Ruby: { lines: 18, time: 1, debug: 0.5 },
|
||||
Go: { lines: 40, time: 2, debug: 0.5 },
|
||||
'Node.js': { lines: 25, time: 1.5, debug: 0.5 },
|
||||
Java: { lines: 80, time: 4, debug: 1 },
|
||||
'C#': { lines: 70, time: 3.5, debug: 1 },
|
||||
Rust: { lines: 50, time: 4, debug: 1 },
|
||||
'C++': { lines: 100, time: 5, debug: 2 },
|
||||
linesInsight:
|
||||
'Python 是脚本自动化的绝对王者,标准库丰富,第三方库如 Pandas、Requests 让数据处理极其简单。Ruby 也很优秀。其他语言对于简单脚本来说都太重量级了。',
|
||||
timeInsight:
|
||||
'Python 和 Ruby 是脚本任务的首选,几行代码就能完成复杂的数据处理。Node.js 在处理 JSON 数据时也很方便。编译型语言(Go、Java、C++)对于简单脚本来说 overhead 太大。',
|
||||
debugInsight:
|
||||
'Python 的交互式解释器(REPL)和丰富的调试工具(pdb、ipdb)让脚本调试极其高效。Ruby 的 Pry 也很强大。其他语言的编译/运行循环对于脚本开发来说太慢了。'
|
||||
},
|
||||
micro: {
|
||||
Python: { lines: 100, time: 6, debug: 3 },
|
||||
Ruby: { lines: 90, time: 5.5, debug: 3.5 },
|
||||
Go: { lines: 120, time: 7, debug: 2 },
|
||||
'Node.js': { lines: 110, time: 6.5, debug: 3 },
|
||||
Java: { lines: 250, time: 15, debug: 4 },
|
||||
'C#': { lines: 200, time: 13, debug: 4 },
|
||||
Rust: { lines: 140, time: 18, debug: 5 },
|
||||
'C++': { lines: 300, time: 22, debug: 8 },
|
||||
linesInsight:
|
||||
'Go 是微服务的理想选择,单一二进制文件 + 内置 HTTP 服务器 + 强大的标准库。Node.js 的 Express/Koa 也很轻量。Python 的 FastAPI 表现不错,但性能和并发不如 Go。',
|
||||
timeInsight:
|
||||
'Go 和 Node.js 在微服务开发中效率最高,启动快,部署简单。Python 和 Ruby 适合快速原型,但生产环境需要更多优化。Java 和 C# 的 Spring Cloud/.NET 虽然强大,但对于简单微服务来说太重量级。',
|
||||
debugInsight:
|
||||
'Go 和 Rust 的类型系统和错误处理让微服务的调试和测试更容易。Java 和 C# 的成熟工具链(JUnit、NUnit)也很有优势。Python 和 Ruby 的动态类型在分布式系统中可能带来运行时错误。'
|
||||
}
|
||||
const taskData = {
|
||||
rest: [
|
||||
{ name: 'Python', time: 4 },
|
||||
{ name: 'Ruby', time: 3.5 },
|
||||
{ name: 'Go', time: 5 },
|
||||
{ name: 'Node.js', time: 4.5 },
|
||||
{ name: 'Java', time: 8 },
|
||||
{ name: 'Rust', time: 10 }
|
||||
],
|
||||
web: [
|
||||
{ name: 'Ruby', time: 9 },
|
||||
{ name: 'Python', time: 10 },
|
||||
{ name: 'Node.js', time: 11 },
|
||||
{ name: 'Go', time: 12 },
|
||||
{ name: 'Java', time: 20 },
|
||||
{ name: 'Rust', time: 25 }
|
||||
],
|
||||
script: [
|
||||
{ name: 'Python', time: 1 },
|
||||
{ name: 'Ruby', time: 1 },
|
||||
{ name: 'Node.js', time: 1.5 },
|
||||
{ name: 'Go', time: 2 },
|
||||
{ name: 'Java', time: 4 },
|
||||
{ name: 'Rust', time: 4 }
|
||||
]
|
||||
}
|
||||
|
||||
const currentMetrics = computed(() => {
|
||||
return taskMetrics[selectedTask.value]
|
||||
})
|
||||
|
||||
const sortedLanguages = computed(() => {
|
||||
return languages
|
||||
.map((lang) => ({
|
||||
name: lang,
|
||||
...currentMetrics.value[lang]
|
||||
}))
|
||||
.sort((a, b) => a.lines - b.lines)
|
||||
return [...taskData[selectedTask.value]].sort((a, b) => a.time - b.time)
|
||||
})
|
||||
|
||||
const getBarWidth = (value) => {
|
||||
const max = Math.max(
|
||||
...Object.values(currentMetrics.value).flatMap((v) => [
|
||||
v.lines,
|
||||
v.time * 20,
|
||||
v.debug * 20
|
||||
])
|
||||
)
|
||||
return (value / max) * 100
|
||||
}
|
||||
|
||||
const getTaskDetail = (taskId) => {
|
||||
return taskMetrics[taskId]
|
||||
}
|
||||
|
||||
const getRadarPosition = (langName) => {
|
||||
const metrics = currentMetrics.value[langName]
|
||||
const avgLines =
|
||||
Object.values(currentMetrics.value).reduce((sum, v) => sum + v.lines, 0) /
|
||||
languages.length
|
||||
const avgTime =
|
||||
Object.values(currentMetrics.value).reduce((sum, v) => sum + v.time, 0) /
|
||||
languages.length
|
||||
const avgDebug =
|
||||
Object.values(currentMetrics.value).reduce((sum, v) => sum + v.debug, 0) /
|
||||
languages.length
|
||||
|
||||
// Normalize metrics (lower is better, so we invert)
|
||||
const linesScore = 1 - metrics.lines / 300 // Max lines ~300
|
||||
const timeScore = 1 - metrics.time / 30 // Max time ~30
|
||||
const debugScore = 1 - metrics.debug / 12 // Max debug ~12
|
||||
|
||||
// Position in 2D space
|
||||
// X: code efficiency (linesScore) vs ecosystem (hardcoded)
|
||||
// Y: speed (timeScore) vs maintainability (debugScore)
|
||||
const x = 50 + (linesScore - 0.5) * 80
|
||||
const y = 50 + (timeScore - 0.5) * 80
|
||||
|
||||
return {
|
||||
left: `${x}%`,
|
||||
top: `${y}%`
|
||||
}
|
||||
}
|
||||
|
||||
const updateMetrics = () => {
|
||||
// Trigger reactivity
|
||||
}
|
||||
const maxTime = computed(() => {
|
||||
return Math.max(...taskData[selectedTask.value].map(l => l.time))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.developer-efficiency-demo {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.task-selector label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.task-selector select {
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 4px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
.efficiency-chart {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.metric-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.metric-header h5 {
|
||||
margin: 0;
|
||||
.chart-header {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.metric-chart {
|
||||
.bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.metric-bar {
|
||||
.bar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
min-width: 70px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
.bar-track {
|
||||
flex: 1;
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg);
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -411,134 +211,22 @@ const updateMetrics = () => {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 0.5rem;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), #8b5cf6);
|
||||
background: var(--vp-c-green-1);
|
||||
transition: width 0.5s ease;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.bar-fill.time {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
}
|
||||
|
||||
.bar-fill.debug {
|
||||
background: linear-gradient(90deg, #f59e0b, #d97706);
|
||||
}
|
||||
|
||||
.metric-insight {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
border-left: 3px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.metric-insight p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.6;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.efficiency-radar {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.efficiency-radar h5 {
|
||||
margin: 0 0 2rem 0;
|
||||
text-align: center;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.radar-chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.radar-grid {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.radar-point {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.radar-point:hover {
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
}
|
||||
|
||||
.point-label {
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.point-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: var(--vp-c-brand);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.radar-legend {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--vp-c-brand);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.metrics-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
min-width: 55px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.efficiency-radar {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.radar-chart {
|
||||
height: 300px;
|
||||
}
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+309
-212
@@ -1,202 +1,323 @@
|
||||
<template>
|
||||
<div class="language-comparison-demo">
|
||||
<div class="comparison-table-wrapper">
|
||||
<table class="comparison-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>维度</th>
|
||||
<th v-for="lang in languages" :key="lang.name" class="lang-col">
|
||||
{{ lang.name }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="dimension in dimensions" :key="dimension.key">
|
||||
<td class="dimension-label">{{ dimension.label }}</td>
|
||||
<td v-for="lang in languages" :key="lang.name" class="score-cell">
|
||||
<div class="score-bar">
|
||||
<div
|
||||
class="score-fill"
|
||||
:class="`score-${dimension.key}`"
|
||||
:style="{ width: getScore(lang.name, dimension.key) + '%' }"
|
||||
>
|
||||
<span class="score-text">{{
|
||||
getScore(lang.name, dimension.key)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="demo-header">
|
||||
<span class="icon">⚖️</span>
|
||||
<span class="title">语言天平</span>
|
||||
<span class="subtitle">权衡不同维度的优劣势</span>
|
||||
</div>
|
||||
|
||||
<div class="insight-panel">
|
||||
<h4>💡 洞察分析</h4>
|
||||
<div class="insight-content">
|
||||
<div class="insight-item">
|
||||
<strong>性能王者:</strong>
|
||||
<span>C++ 和 Rust 在性能方面遥遥领先,但学习曲线极其陡峭。</span>
|
||||
</div>
|
||||
<div class="insight-item">
|
||||
<strong>开发效率:</strong>
|
||||
<span
|
||||
>Python 和 Ruby 在快速开发方面无与伦比,适合原型和初创公司。</span
|
||||
>
|
||||
</div>
|
||||
<div class="insight-item">
|
||||
<strong>生态成熟度:</strong>
|
||||
<span
|
||||
>Java、Python、Node.js 拥有最成熟的生态系统,库和框架丰富。</span
|
||||
>
|
||||
</div>
|
||||
<div class="insight-item">
|
||||
<strong>学习曲线:</strong>
|
||||
<span
|
||||
>Python、Ruby、Go 最容易上手,Rust 和 C++ 需要较长时间学习。</span
|
||||
>
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">超市购物</span>:有的商品便宜但不耐用,有的质量好但价格高。选择后端语言也一样,需要在性能、开发效率、生态成熟度等多个维度之间做权衡。
|
||||
</div>
|
||||
|
||||
<div class="dimension-selector">
|
||||
<div class="dimension-label">选择比较维度:</div>
|
||||
<div class="dimension-buttons">
|
||||
<button
|
||||
v-for="dim in dimensions"
|
||||
:key="dim.key"
|
||||
class="dimension-btn"
|
||||
:class="{ active: selectedDimension === dim.key }"
|
||||
@click="selectedDimension = dim.key"
|
||||
>
|
||||
<span class="dim-icon">{{ dim.icon }}</span>
|
||||
<span class="dim-label">{{ dim.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-chart">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">{{ getDimensionInfo().title }}</span>
|
||||
<span class="chart-unit">{{ getDimensionInfo().unit }}</span>
|
||||
</div>
|
||||
<div class="bars-container">
|
||||
<div
|
||||
v-for="lang in sortedLanguages"
|
||||
:key="lang.name"
|
||||
class="bar-wrapper"
|
||||
>
|
||||
<div class="bar-label">{{ lang.name }}</div>
|
||||
<div class="bar-track">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:class="getBarClass(lang.score)"
|
||||
:style="{ width: lang.score + '%' }"
|
||||
>
|
||||
<span class="bar-value">{{ lang.score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="insight-box">
|
||||
<span class="icon">🔍</span>
|
||||
<div class="insight-content">
|
||||
<strong>洞察分析:</strong>
|
||||
<p>{{ getDimensionInfo().insight }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>没有"万能银弹"。高性能往往意味着高开发成本(C++、Rust),快速开发通常伴随性能损失(Python、Ruby)。根据项目核心诉求做取舍,而不是追求"样样都行"。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const languages = [
|
||||
{ name: 'Java' },
|
||||
{ name: 'Python' },
|
||||
{ name: 'Go' },
|
||||
{ name: 'Node.js' },
|
||||
{ name: 'Rust' },
|
||||
{ name: 'C++' }
|
||||
]
|
||||
const selectedDimension = ref('performance')
|
||||
|
||||
const dimensions = [
|
||||
{ key: 'performance', label: '性能' },
|
||||
{ key: 'efficiency', label: '开发效率' },
|
||||
{ key: 'ecosystem', label: '生态成熟度' },
|
||||
{ key: 'learning', label: '学习曲线' },
|
||||
{ key: 'concurrency', label: '并发能力' },
|
||||
{ key: 'memory', label: '内存管理' }
|
||||
{ key: 'performance', icon: '⚡', label: '性能' },
|
||||
{ key: 'efficiency', icon: '🚀', label: '开发效率' },
|
||||
{ key: 'ecosystem', icon: '📦', label: '生态成熟度' },
|
||||
{ key: 'learning', icon: '📚', label: '学习曲线' },
|
||||
{ key: 'concurrency', icon: '🔄', label: '并发能力' }
|
||||
]
|
||||
|
||||
const scores = {
|
||||
Java: {
|
||||
performance: 75,
|
||||
efficiency: 60,
|
||||
ecosystem: 95,
|
||||
learning: 40,
|
||||
concurrency: 80,
|
||||
memory: 70
|
||||
const dimensionInfo = {
|
||||
performance: {
|
||||
title: '性能对比',
|
||||
unit: '(分数越高越快)',
|
||||
insight: 'C++ 和 Rust 在性能方面遥遥领先,但学习曲线极其陡峭。Go 和 Java 在性能和开发效率之间取得了很好的平衡。Python 和 Ruby 性能最弱,但开发速度最快。'
|
||||
},
|
||||
Python: {
|
||||
performance: 30,
|
||||
efficiency: 95,
|
||||
ecosystem: 95,
|
||||
learning: 95,
|
||||
concurrency: 30,
|
||||
memory: 40
|
||||
efficiency: {
|
||||
title: '开发效率',
|
||||
unit: '(分数越高越快)',
|
||||
insight: 'Python 和 Ruby 在快速开发方面无与伦比,适合原型和初创公司。Go 和 Node.js 居中,兼顾了开发速度和性能。Rust 和 C++ 开发效率最低,主要受学习曲线影响。'
|
||||
},
|
||||
Go: {
|
||||
performance: 90,
|
||||
efficiency: 85,
|
||||
ecosystem: 75,
|
||||
learning: 80,
|
||||
concurrency: 95,
|
||||
memory: 85
|
||||
ecosystem: {
|
||||
title: '生态成熟度',
|
||||
unit: '(分数越高库越多)',
|
||||
insight: 'Java、Python、Node.js 拥有最成熟的生态系统。Go 和 Rust 虽然年轻,但发展迅速。C++ 生态成熟但学习成本高。Ruby 生态主要集中在 Web 开发领域。'
|
||||
},
|
||||
'Node.js': {
|
||||
performance: 70,
|
||||
efficiency: 85,
|
||||
ecosystem: 95,
|
||||
learning: 75,
|
||||
concurrency: 85,
|
||||
memory: 75
|
||||
learning: {
|
||||
title: '学习曲线',
|
||||
unit: '(分数越高越简单)',
|
||||
insight: 'Python、Ruby、Go 最容易上手。Node.js 需要理解异步概念。Java 需要掌握面向对象和框架。Rust 和 C++ 学习曲线最陡,需要深入理解内存管理。'
|
||||
},
|
||||
Rust: {
|
||||
performance: 95,
|
||||
efficiency: 40,
|
||||
ecosystem: 70,
|
||||
learning: 20,
|
||||
concurrency: 90,
|
||||
memory: 98
|
||||
},
|
||||
'C++': {
|
||||
performance: 98,
|
||||
efficiency: 35,
|
||||
ecosystem: 90,
|
||||
learning: 25,
|
||||
concurrency: 85,
|
||||
memory: 70
|
||||
concurrency: {
|
||||
title: '并发能力',
|
||||
unit: '(分数越高越强)',
|
||||
insight: 'Go 的 Goroutine 是并发的王者,轻量且简单。Rust 的异步模型性能强大但复杂。Java 的线程池成熟稳定。Node.js 的事件循环适合 I/O 密集型。Python 的 GIL 限制了多线程性能。'
|
||||
}
|
||||
}
|
||||
|
||||
const getScore = (lang, dimension) => {
|
||||
return scores[lang][dimension]
|
||||
const languageScores = {
|
||||
performance: [
|
||||
{ name: 'C++', score: 98 },
|
||||
{ name: 'Rust', score: 95 },
|
||||
{ name: 'Go', score: 90 },
|
||||
{ name: 'Java', score: 75 },
|
||||
{ name: 'Node.js', score: 70 },
|
||||
{ name: 'Python', score: 30 },
|
||||
{ name: 'Ruby', score: 25 }
|
||||
],
|
||||
efficiency: [
|
||||
{ name: 'Python', score: 95 },
|
||||
{ name: 'Ruby', score: 90 },
|
||||
{ name: 'Go', score: 85 },
|
||||
{ name: 'Node.js', score: 85 },
|
||||
{ name: 'Java', score: 60 },
|
||||
{ name: 'Rust', score: 40 },
|
||||
{ name: 'C++', score: 35 }
|
||||
],
|
||||
ecosystem: [
|
||||
{ name: 'Java', score: 95 },
|
||||
{ name: 'Python', score: 95 },
|
||||
{ name: 'Node.js', score: 95 },
|
||||
{ name: 'C++', score: 90 },
|
||||
{ name: 'Go', score: 75 },
|
||||
{ name: 'Ruby', score: 70 },
|
||||
{ name: 'Rust', score: 70 }
|
||||
],
|
||||
learning: [
|
||||
{ name: 'Python', score: 95 },
|
||||
{ name: 'Ruby', score: 85 },
|
||||
{ name: 'Go', score: 80 },
|
||||
{ name: 'Node.js', score: 75 },
|
||||
{ name: 'Java', score: 40 },
|
||||
{ name: 'C++', score: 25 },
|
||||
{ name: 'Rust', score: 20 }
|
||||
],
|
||||
concurrency: [
|
||||
{ name: 'Go', score: 95 },
|
||||
{ name: 'Rust', score: 90 },
|
||||
{ name: 'Node.js', score: 85 },
|
||||
{ name: 'Java', score: 80 },
|
||||
{ name: 'C++', score: 85 },
|
||||
{ name: 'Python', score: 30 },
|
||||
{ name: 'Ruby', score: 25 }
|
||||
]
|
||||
}
|
||||
|
||||
const sortedLanguages = computed(() => {
|
||||
const scores = languageScores[selectedDimension.value]
|
||||
return [...scores].sort((a, b) => b.score - a.score)
|
||||
})
|
||||
|
||||
const getDimensionInfo = () => {
|
||||
return dimensionInfo[selectedDimension.value]
|
||||
}
|
||||
|
||||
const getBarClass = (score) => {
|
||||
if (score >= 85) return 'bar-high'
|
||||
if (score >= 60) return 'bar-medium'
|
||||
return 'bar-low'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.language-comparison-demo {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.comparison-table-wrapper {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.comparison-table th {
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
border-bottom: 2px solid var(--vp-c-divider);
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.comparison-table th.lang-col {
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.1rem;
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.comparison-table td {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dimension-selector {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.dimension-label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.score-cell {
|
||||
padding: 0.75rem;
|
||||
.dimension-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.score-bar {
|
||||
height: 30px;
|
||||
.dimension-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.score-fill {
|
||||
.dimension-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.dimension-btn.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dim-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.comparison-chart {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.chart-unit {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.bars-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.bar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
min-width: 70px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.bar-track {
|
||||
flex: 1;
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -205,86 +326,62 @@ const getScore = (lang, dimension) => {
|
||||
transition: width 0.5s ease;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.score-performance {
|
||||
background: linear-gradient(90deg, #fbbf24, #f59e0b);
|
||||
.bar-high {
|
||||
background: var(--vp-c-green-1);
|
||||
}
|
||||
|
||||
.score-efficiency {
|
||||
background: linear-gradient(90deg, #34d399, #10b981);
|
||||
.bar-medium {
|
||||
background: var(--vp-c-yellow-1);
|
||||
}
|
||||
|
||||
.score-ecosystem {
|
||||
background: linear-gradient(90deg, #60a5fa, #3b82f6);
|
||||
.bar-low {
|
||||
background: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.score-learning {
|
||||
background: linear-gradient(90deg, #f472b6, #ec4899);
|
||||
}
|
||||
|
||||
.score-concurrency {
|
||||
background: linear-gradient(90deg, #a78bfa, #8b5cf6);
|
||||
}
|
||||
|
||||
.score-memory {
|
||||
background: linear-gradient(90deg, #fb923c, #ea580c);
|
||||
}
|
||||
|
||||
.score-text {
|
||||
color: white;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.insight-panel {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.insight-panel h4 {
|
||||
margin-top: 0;
|
||||
.insight-box {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.2rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
border-left: 3px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.insight-box .icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.insight-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.insight-item {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.insight-item strong {
|
||||
.insight-content strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.insight-item span {
|
||||
.insight-content p {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comparison-table-wrapper {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.comparison-table th,
|
||||
.comparison-table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.insight-content {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+116
-908
File diff suppressed because it is too large
Load Diff
+240
-463
@@ -1,593 +1,370 @@
|
||||
<template>
|
||||
<div class="language-selector-demo">
|
||||
<div class="selector-header">
|
||||
<h4>🎯 语言选择器</h4>
|
||||
<p class="subtitle">根据项目需求选择最适合的后端语言</p>
|
||||
<div class="demo-header">
|
||||
<span class="icon">🎯</span>
|
||||
<span class="title">语言选择器</span>
|
||||
<span class="subtitle">根据需求选择最合适的后端语言</span>
|
||||
</div>
|
||||
|
||||
<div class="selection-flow">
|
||||
<!-- Step 1: Project Type -->
|
||||
<div class="step-card" :class="{ active: currentStep === 1 }">
|
||||
<div class="step-number">1</div>
|
||||
<h5>项目类型</h5>
|
||||
<div class="options-grid">
|
||||
<button
|
||||
v-for="option in projectTypes"
|
||||
:key="option.id"
|
||||
class="option-btn"
|
||||
:class="{ selected: answers.projectType === option.id }"
|
||||
@click="selectProjectType(option.id)"
|
||||
>
|
||||
<span class="option-icon">{{ option.icon }}</span>
|
||||
<span class="option-label">{{ option.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">点餐</span>:想吃快餐选 Python(快速),想吃大餐选 Java(正式),想吃健康餐选 Go(平衡)。没有"最好的"选择,只有"最合适"的选择。
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Performance Requirement -->
|
||||
<div class="questions-container">
|
||||
<div
|
||||
class="step-card"
|
||||
:class="{ active: currentStep === 2, disabled: !answers.projectType }"
|
||||
v-for="(question, index) in questions"
|
||||
:key="question.id"
|
||||
class="question-card"
|
||||
:class="{ active: currentQuestion === index }"
|
||||
>
|
||||
<div class="step-number">2</div>
|
||||
<h5>性能要求</h5>
|
||||
<div class="options-grid">
|
||||
<button
|
||||
v-for="option in performanceLevels"
|
||||
:key="option.id"
|
||||
class="option-btn"
|
||||
:class="{ selected: answers.performance === option.id }"
|
||||
@click="selectPerformance(option.id)"
|
||||
>
|
||||
<span class="option-icon">{{ option.icon }}</span>
|
||||
<span class="option-label">{{ option.label }}</span>
|
||||
<span class="option-desc">{{ option.desc }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Team Background -->
|
||||
<div
|
||||
class="step-card"
|
||||
:class="{
|
||||
active: currentStep === 3,
|
||||
disabled: !answers.performance
|
||||
}"
|
||||
>
|
||||
<div class="step-number">3</div>
|
||||
<h5>团队背景</h5>
|
||||
<div class="options-grid">
|
||||
<button
|
||||
v-for="option in teamBackgrounds"
|
||||
:key="option.id"
|
||||
class="option-btn"
|
||||
:class="{ selected: answers.team === option.id }"
|
||||
@click="selectTeam(option.id)"
|
||||
>
|
||||
<span class="option-icon">{{ option.icon }}</span>
|
||||
<span class="option-label">{{ option.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 4: Time to Market -->
|
||||
<div
|
||||
class="step-card"
|
||||
:class="{ active: currentStep === 4, disabled: !answers.team }"
|
||||
>
|
||||
<div class="step-number">4</div>
|
||||
<h5>上市时间</h5>
|
||||
<div class="options-grid">
|
||||
<button
|
||||
v-for="option in timeConstraints"
|
||||
:key="option.id"
|
||||
class="option-btn"
|
||||
:class="{ selected: answers.time === option.id }"
|
||||
@click="selectTime(option.id)"
|
||||
>
|
||||
<span class="option-icon">{{ option.icon }}</span>
|
||||
<span class="option-label">{{ option.label }}</span>
|
||||
<span class="option-desc">{{ option.desc }}</span>
|
||||
</button>
|
||||
<div class="question-number">{{ index + 1 }}</div>
|
||||
<div class="question-content">
|
||||
<h6>{{ question.text }}</h6>
|
||||
<div class="options">
|
||||
<button
|
||||
v-for="option in question.options"
|
||||
:key="option.value"
|
||||
class="option-btn"
|
||||
:class="{ selected: answers[index] === option.value }"
|
||||
@click="selectAnswer(index, option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommendation -->
|
||||
<transition name="result">
|
||||
<Transition name="fade">
|
||||
<div v-if="recommendation" class="recommendation-panel">
|
||||
<h5>🎉 推荐语言</h5>
|
||||
<div class="recommendation-card">
|
||||
<div class="rec-language">
|
||||
<span class="rec-icon">{{ recommendation.icon }}</span>
|
||||
<span class="rec-name">{{ recommendation.language }}</span>
|
||||
</div>
|
||||
<div class="rec-reason">
|
||||
<h6>选择理由</h6>
|
||||
<p>{{ recommendation.reason }}</p>
|
||||
</div>
|
||||
<div class="rec-alternatives">
|
||||
<h6>备选方案</h6>
|
||||
<div class="alt-list">
|
||||
<span
|
||||
v-for="alt in recommendation.alternatives"
|
||||
:key="alt"
|
||||
class="alt-tag"
|
||||
>
|
||||
{{ alt }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rec-header">
|
||||
<span class="rec-icon">{{ recommendation.icon }}</span>
|
||||
<div class="rec-title">
|
||||
<h6>推荐语言</h6>
|
||||
<div class="rec-name">{{ recommendation.language }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rec-reason">
|
||||
<strong>选择理由:</strong>
|
||||
<p>{{ recommendation.reason }}</p>
|
||||
</div>
|
||||
<button class="reset-btn" @click="reset">🔄 重新选择</button>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>不要问"哪个语言最火",而要问"我的项目需要什么"。初创公司优先开发速度(Python/Node.js),大厂优先稳定性和性能(Java/Go),系统编程优先安全(Rust)。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const currentStep = ref(1)
|
||||
const answers = ref({
|
||||
projectType: null,
|
||||
performance: null,
|
||||
team: null,
|
||||
time: null
|
||||
})
|
||||
const currentQuestion = ref(0)
|
||||
const answers = ref({})
|
||||
|
||||
const projectTypes = [
|
||||
{ id: 'web', icon: '🌐', label: 'Web 应用' },
|
||||
{ id: 'api', icon: '🔌', label: 'API 服务' },
|
||||
{ id: 'microservice', icon: '⚙️', label: '微服务' },
|
||||
{ id: 'ai', icon: '🤖', label: 'AI/ML' },
|
||||
{ id: 'game', icon: '🎮', label: '游戏' },
|
||||
{ id: 'system', icon: '💻', label: '系统编程' }
|
||||
]
|
||||
|
||||
const performanceLevels = [
|
||||
{ id: 'extreme', icon: '⚡', label: '极致性能', desc: '每秒百万级请求' },
|
||||
{ id: 'high', icon: '🚀', label: '高性能', desc: '每秒十万级请求' },
|
||||
{ id: 'medium', icon: '📊', label: '中等性能', desc: '每秒万级请求' },
|
||||
{ id: 'low', icon: '🐌', label: '性能不敏感', desc: '快速开发优先' }
|
||||
]
|
||||
|
||||
const teamBackgrounds = [
|
||||
{ id: 'frontend', icon: '💚', label: '前端团队' },
|
||||
{ id: 'python', icon: '🐍', label: 'Python 背景' },
|
||||
{ id: 'java', icon: '☕', label: 'Java 背景' },
|
||||
{ id: 'mixed', icon: '👥', label: '混合团队' },
|
||||
{ id: 'new', icon: '🆕', label: '新团队' }
|
||||
]
|
||||
|
||||
const timeConstraints = [
|
||||
{ id: 'urgent', icon: '🔥', label: '紧急', desc: '1-2 周上线' },
|
||||
{ id: 'normal', icon: '📅', label: '正常', desc: '1-2 个月' },
|
||||
{ id: 'flexible', icon: '🎯', label: '灵活', desc: '3 个月以上' }
|
||||
const questions = [
|
||||
{
|
||||
id: 'project_type',
|
||||
text: '项目类型是什么?',
|
||||
options: [
|
||||
{ value: 'web', label: 'Web 应用' },
|
||||
{ value: 'api', label: 'API 服务' },
|
||||
{ value: 'ai', label: 'AI/ML' },
|
||||
{ value: 'system', label: '系统编程' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'performance',
|
||||
text: '性能要求如何?',
|
||||
options: [
|
||||
{ value: 'high', label: '高性能' },
|
||||
{ value: 'medium', label: '中等' },
|
||||
{ value: 'low', label: '不敏感' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'team',
|
||||
text: '团队背景?',
|
||||
options: [
|
||||
{ value: 'frontend', label: '前端团队' },
|
||||
{ value: 'python', label: 'Python 背景' },
|
||||
{ value: 'java', label: 'Java 背景' },
|
||||
{ value: 'new', label: '新团队' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const recommendation = computed(() => {
|
||||
if (
|
||||
!answers.value.projectType ||
|
||||
!answers.value.performance ||
|
||||
!answers.value.team ||
|
||||
!answers.value.time
|
||||
) {
|
||||
return null
|
||||
}
|
||||
if (Object.keys(answers.value).length < 3) return null
|
||||
|
||||
const { projectType, performance, team, time } = answers.value
|
||||
const { project_type, performance, team } = answers.value
|
||||
|
||||
// 决策逻辑
|
||||
if (projectType === 'ai') {
|
||||
if (project_type === 'ai') {
|
||||
return {
|
||||
language: 'Python',
|
||||
icon: '🐍',
|
||||
reason:
|
||||
'AI/ML 领域的绝对统治地位,生态无与伦比(NumPy、PyTorch、TensorFlow)。虽然性能不如 C++/Rust,但 95% 的 AI 项目都在用 Python。',
|
||||
alternatives: ['C++ (模型部署)', 'Julia (科学计算)']
|
||||
language: 'Python',
|
||||
reason: 'AI/ML 的绝对统治地位,生态无与伦比。虽然性能不如 C++/Rust,但 95% 的 AI 项目都在用 Python。'
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === 'game') {
|
||||
if (project_type === 'system' || performance === 'high') {
|
||||
return {
|
||||
language: 'C++',
|
||||
icon: '⚡',
|
||||
reason:
|
||||
'游戏开发的行业标准(Unreal Engine)。极致性能,底层控制力强。如果使用 Unity 引擎,则 C# 是首选。',
|
||||
alternatives: ['C# (Unity)', 'Rust (独立游戏)']
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === 'system') {
|
||||
if (performance === 'extreme') {
|
||||
return {
|
||||
language: 'Rust',
|
||||
icon: '🦀',
|
||||
reason:
|
||||
'内存安全 + 极致性能,现代化系统语言。虽然学习曲线陡峭,但编译时保证无内存泄漏,适合长期维护的基础设施。',
|
||||
alternatives: ['C++ (传统选择)', 'Go (云原生)']
|
||||
}
|
||||
}
|
||||
return {
|
||||
language: 'Go',
|
||||
icon: '🐹',
|
||||
reason:
|
||||
'云原生时代的宠儿(Docker、K8s 都是 Go 写的)。简洁语法 + 原生并发 + 快速编译,非常适合系统编程和 DevOps 工具。',
|
||||
alternatives: ['Rust (更安全)', 'C++ (更成熟)']
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === 'microservice') {
|
||||
if (performance === 'extreme' || performance === 'high') {
|
||||
return {
|
||||
language: 'Go',
|
||||
icon: '🐹',
|
||||
reason:
|
||||
'云原生的首选语言。Goroutine 轻量级并发可轻松处理百万级请求,编译后的单一可执行文件部署极其简单。',
|
||||
alternatives: ['Java (Spring Cloud)', 'Rust (极致性能)']
|
||||
}
|
||||
}
|
||||
return {
|
||||
language: 'Node.js',
|
||||
icon: '💚',
|
||||
reason:
|
||||
'前后端统一,减少语言切换成本。NPM 生态丰富,适合 I/O 密集型的微服务。',
|
||||
alternatives: ['Go (更高性能)', 'Python (快速开发)']
|
||||
language: 'Go',
|
||||
reason: '云原生时代的宠儿,简洁语法 + 原生并发 + 快速编译。单一可执行文件部署极其简单。'
|
||||
}
|
||||
}
|
||||
|
||||
if (team === 'frontend') {
|
||||
return {
|
||||
language: 'Node.js',
|
||||
icon: '💚',
|
||||
reason:
|
||||
'前端团队零学习成本,TypeScript 提供类型安全。全栈开发减少沟通成本,适合快速迭代和 MVP 开发。',
|
||||
alternatives: ['Go (后端性能优化)', 'TypeScript (类型安全)']
|
||||
language: 'Node.js',
|
||||
reason: '前后端统一,减少语言切换成本。NPM 生态庞大,适合快速迭代和 MVP 开发。'
|
||||
}
|
||||
}
|
||||
|
||||
if (team === 'python') {
|
||||
return {
|
||||
language: 'Python',
|
||||
icon: '🐍',
|
||||
reason:
|
||||
'利用团队现有技能,快速开发。Django/FastAPI 生态成熟,适合数据驱动的 Web 应用。',
|
||||
alternatives: ['Go (性能提升)', 'Node.js (全栈)']
|
||||
language: 'Python',
|
||||
reason: '利用团队现有技能,快速开发。Django/FastAPI 生态成熟,适合数据驱动的应用。'
|
||||
}
|
||||
}
|
||||
|
||||
if (team === 'java') {
|
||||
return {
|
||||
language: 'Java',
|
||||
icon: '☕',
|
||||
reason:
|
||||
'企业级开发的最佳选择。Spring Boot 生态极其成熟,团队熟悉度高,维护成本最低。',
|
||||
alternatives: ['Go (云原生)', 'Kotlin (更现代)']
|
||||
language: 'Java',
|
||||
reason: '企业级开发的最佳选择。Spring Boot 生态极其成熟,团队熟悉度高,维护成本低。'
|
||||
}
|
||||
}
|
||||
|
||||
if (time === 'urgent') {
|
||||
if (projectType === 'web' || projectType === 'api') {
|
||||
return {
|
||||
language: 'Python',
|
||||
icon: '🐍',
|
||||
reason:
|
||||
'开发速度最快的语言。FastAPI/Django 让你在几天内就能搭建起完整的 Web 应用,适合快速验证想法和 MVP。',
|
||||
alternatives: ['Ruby (Rails)', 'Node.js (全栈)']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认推荐
|
||||
if (performance === 'extreme') {
|
||||
return {
|
||||
language: 'Go',
|
||||
icon: '🐹',
|
||||
reason:
|
||||
'高性能 + 简洁语法 + 快速开发的最佳平衡点。Goroutine 并发模型让处理高并发变得简单,而不会像 Java 那么复杂。',
|
||||
alternatives: ['Rust (更安全)', 'C++ (更极致)']
|
||||
}
|
||||
}
|
||||
|
||||
if (performance === 'high') {
|
||||
return {
|
||||
language: 'Go',
|
||||
icon: '🐹',
|
||||
reason:
|
||||
'云原生时代的高性能语言。相比 Java 更简洁,相比 Node.js 性能更好,相比 C++ 更容易维护。',
|
||||
alternatives: ['Java (更成熟)', 'Node.js (更灵活)']
|
||||
}
|
||||
}
|
||||
|
||||
// Default: Node.js or Python
|
||||
return {
|
||||
language: 'Node.js',
|
||||
icon: '💚',
|
||||
reason:
|
||||
'前后端统一,生态庞大,适合大多数 Web 应用和 API 服务。NPM 拥有世界最大的包仓库,几乎任何功能都能找到现成的库。',
|
||||
alternatives: ['Go (更高性能)', 'Python (更简单)']
|
||||
icon: '🐹',
|
||||
language: 'Go',
|
||||
reason: '云原生时代的高性能语言。相比 Java 更简洁,相比 Node.js 性能更好,相比 Python 更稳定。'
|
||||
}
|
||||
})
|
||||
|
||||
const selectProjectType = (value) => {
|
||||
answers.value.projectType = value
|
||||
currentStep.value = 2
|
||||
}
|
||||
|
||||
const selectPerformance = (value) => {
|
||||
answers.value.performance = value
|
||||
currentStep.value = 3
|
||||
}
|
||||
|
||||
const selectTeam = (value) => {
|
||||
answers.value.team = value
|
||||
currentStep.value = 4
|
||||
}
|
||||
|
||||
const selectTime = (value) => {
|
||||
answers.value.time = value
|
||||
currentStep.value = 5
|
||||
const selectAnswer = (questionIndex, value) => {
|
||||
answers.value[questionIndex] = value
|
||||
if (currentQuestion.value < questions.length - 1) {
|
||||
currentQuestion.value++
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
answers.value = {
|
||||
projectType: null,
|
||||
performance: null,
|
||||
team: null,
|
||||
time: null
|
||||
}
|
||||
currentStep.value = 1
|
||||
answers.value = {}
|
||||
currentQuestion.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.language-selector-demo {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.selector-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.selector-header h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.selection-flow {
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.questions-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.step-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 8px 30px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.step-card.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
font-weight: bold;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.step-card h5 {
|
||||
margin: 0 0 1.5rem 0;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.options-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.option-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1.5rem 1rem;
|
||||
.question-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
border: 2px solid transparent;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.option-btn:hover:not(.selected) {
|
||||
.question-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
.option-btn.selected {
|
||||
border-color: var(--vp-c-brand);
|
||||
.question-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
font-size: 2rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.option-desc {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.8;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recommendation-panel {
|
||||
margin-top: 3rem;
|
||||
animation: slide-up 0.5s ease;
|
||||
}
|
||||
|
||||
.recommendation-panel h5 {
|
||||
text-align: center;
|
||||
margin: 0 0 2rem 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.recommendation-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem;
|
||||
color: white;
|
||||
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.rec-language {
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.question-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.question-content h6 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.option-btn {
|
||||
padding: 0.35rem 0.75rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.option-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.option-btn.selected {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.recommendation-panel {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
border: 2px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.rec-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.rec-icon {
|
||||
font-size: 4rem;
|
||||
line-height: 1;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.rec-title h6 {
|
||||
margin: 0 0 0.25rem 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.rec-name {
|
||||
font-size: 2.5rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.rec-reason,
|
||||
.rec-alternatives {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
backdrop-filter: blur(10px);
|
||||
.rec-reason {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.rec-reason h6,
|
||||
.rec-alternatives h6 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
.rec-reason strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.rec-reason p {
|
||||
margin: 0;
|
||||
line-height: 1.7;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.alt-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.alt-tag {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
color: #667eea;
|
||||
padding: 0.5rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
background: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.result-enter-active,
|
||||
.result-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.result-enter-from {
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
.result-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.options-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.rec-name {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.rec-icon {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+117
-27
@@ -1,18 +1,40 @@
|
||||
<template>
|
||||
<div class="memory-management-demo">
|
||||
<div class="demo-header">
|
||||
<h4>内存管理机制可视化</h4>
|
||||
<span class="icon">🧠</span>
|
||||
<span class="title">内存管理</span>
|
||||
<span class="subtitle">不同语言的内存处理方式</span>
|
||||
</div>
|
||||
<div class="memory-models">
|
||||
<div class="model-card" v-for="model in models" :key="model.name">
|
||||
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">收拾房间</span>:有的房间有自动扫地机器人定期清理(GC),有的需要自己动手整理(手动管理),有的房间设计得不会变乱(所有权系统)。
|
||||
</div>
|
||||
|
||||
<div class="models-container">
|
||||
<div
|
||||
v-for="model in models"
|
||||
:key="model.name"
|
||||
class="model-card"
|
||||
>
|
||||
<div class="model-icon">{{ model.icon }}</div>
|
||||
<div class="model-name">{{ model.name }}</div>
|
||||
<div class="model-desc">{{ model.desc }}</div>
|
||||
<div class="model-desc">{{ model.description }}</div>
|
||||
<div class="model-languages">
|
||||
<span v-for="lang in model.languages" :key="lang" class="lang-tag">{{ lang }}</span>
|
||||
<span
|
||||
v-for="lang in model.languages"
|
||||
:key="lang"
|
||||
class="lang-tag"
|
||||
>
|
||||
{{ lang }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>GC 语言(Java、Go、Python)让开发者省心,但有性能开销。手动管理(C、C++)性能最好但容易内存泄漏。Rust 的所有权系统编译时保证安全,无运行时开销。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -21,19 +43,19 @@ const models = [
|
||||
{
|
||||
name: '垃圾回收 (GC)',
|
||||
icon: '♻️',
|
||||
desc: '运行时自动回收不再使用的内存',
|
||||
description: '运行时自动回收不再使用的内存',
|
||||
languages: ['Java', 'Go', 'Python', 'Node.js']
|
||||
},
|
||||
{
|
||||
name: '手动管理',
|
||||
icon: '🔧',
|
||||
desc: '开发者显式申请和释放内存',
|
||||
description: '开发者显式申请和释放内存',
|
||||
languages: ['C', 'C++']
|
||||
},
|
||||
{
|
||||
name: '所有权系统',
|
||||
icon: '🔒',
|
||||
desc: '编译时通过规则保证内存安全',
|
||||
description: '编译时通过规则保证内存安全',
|
||||
languages: ['Rust']
|
||||
}
|
||||
]
|
||||
@@ -43,41 +65,109 @@ const models = [
|
||||
.memory-management-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.memory-models {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.model-card {
|
||||
padding: 16px;
|
||||
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.models-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.model-icon {
|
||||
font-size: 2em;
|
||||
margin-bottom: 8px;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.model-desc {
|
||||
font-size: 0.9em;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.model-languages {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lang-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
margin: 2px;
|
||||
background: var(--vp-c-bg-mute);
|
||||
padding: 0.2rem 0.5rem;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+177
-224
@@ -1,45 +1,41 @@
|
||||
<template>
|
||||
<div class="performance-benchmark-demo">
|
||||
<div class="benchmark-controls">
|
||||
<h4>性能基准测试</h4>
|
||||
<div class="control-group">
|
||||
<label>测试场景:</label>
|
||||
<div class="demo-header">
|
||||
<span class="icon">🏁</span>
|
||||
<span class="title">性能赛道</span>
|
||||
<span class="subtitle">不同语言的竞速测试</span>
|
||||
</div>
|
||||
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">赛车场</span>:F1 赛车(C++、Rust)速度极快但难以驾驭,家用轿车(Python、Ruby)舒适但速度慢,跑车(Go、Java)在速度和操控之间取得平衡。
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
<div class="scenario-selector">
|
||||
<label>选择赛道:</label>
|
||||
<select v-model="selectedScenario" @change="runBenchmark">
|
||||
<option value="hello">Hello World (简单 HTTP)</option>
|
||||
<option value="json">JSON 序列化</option>
|
||||
<option value="db">数据库查询</option>
|
||||
<option value="compute">CPU 密集计算</option>
|
||||
<option v-for="scenario in scenarios" :key="scenario.id" :value="scenario.id">
|
||||
{{ scenario.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="run-btn" @click="runBenchmark" :disabled="isRunning">
|
||||
{{ isRunning ? '测试中...' : '▶ 运行测试' }}
|
||||
{{ isRunning ? '测试中...' : '▶ 开始测试' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="results-panel">
|
||||
<div class="results-header">
|
||||
<h5>测试结果(Requests/Second)</h5>
|
||||
<div class="legend">
|
||||
<span class="legend-item"
|
||||
><span class="dot" style="background: #f59e0b"></span> 高性能</span
|
||||
>
|
||||
<span class="legend-item"
|
||||
><span class="dot" style="background: #10b981"></span> 中等</span
|
||||
>
|
||||
<span class="legend-item"
|
||||
><span class="dot" style="background: #6366f1"></span> 较低</span
|
||||
>
|
||||
</div>
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">测试结果(Requests/Second)</span>
|
||||
</div>
|
||||
|
||||
<transition-group name="bar" tag="div" class="chart-container">
|
||||
<div class="bars-container">
|
||||
<div
|
||||
v-for="result in sortedResults"
|
||||
:key="result.language"
|
||||
class="chart-bar-wrapper"
|
||||
class="bar-wrapper"
|
||||
>
|
||||
<div class="bar-label">{{ result.language }}</div>
|
||||
<div class="bar-container">
|
||||
<div class="bar-track">
|
||||
<div
|
||||
class="bar-fill"
|
||||
:class="getBarClass(result.rps)"
|
||||
@@ -49,14 +45,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="explanation-panel">
|
||||
<h5>💡 性能解释</h5>
|
||||
<div class="explanation-content">
|
||||
<p>{{ getCurrentExplanation() }}</p>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<span>{{ getCurrentExplanation() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -67,56 +61,57 @@ import { ref, computed } from 'vue'
|
||||
const selectedScenario = ref('hello')
|
||||
const isRunning = ref(false)
|
||||
|
||||
const scenarios = [
|
||||
{ id: 'hello', label: '🏁 简单 HTTP (Hello World)' },
|
||||
{ id: 'json', label: '📦 JSON 序列化' },
|
||||
{ id: 'db', label: '🗄️ 数据库查询' },
|
||||
{ id: 'compute', label: '⚙️ CPU 密集计算' }
|
||||
]
|
||||
|
||||
const benchmarkData = {
|
||||
hello: {
|
||||
'C++': { rps: 1500000, time: 0.5 },
|
||||
Rust: { rps: 1200000, time: 0.6 },
|
||||
Go: { rps: 1000000, time: 0.7 },
|
||||
Java: { rps: 700000, time: 1.0 },
|
||||
'Node.js': { rps: 800000, time: 0.9 },
|
||||
Python: { rps: 200000, time: 2.5 },
|
||||
Ruby: { rps: 150000, time: 3.0 },
|
||||
PHP: { rps: 250000, time: 2.0 }
|
||||
},
|
||||
json: {
|
||||
'C++': { rps: 800000, time: 1.0 },
|
||||
Rust: { rps: 700000, time: 1.1 },
|
||||
Go: { rps: 600000, time: 1.2 },
|
||||
Java: { rps: 500000, time: 1.5 },
|
||||
'Node.js': { rps: 450000, time: 1.6 },
|
||||
Python: { rps: 150000, time: 4.0 },
|
||||
Ruby: { rps: 120000, time: 5.0 },
|
||||
PHP: { rps: 180000, time: 3.5 }
|
||||
},
|
||||
db: {
|
||||
'C++': { rps: 300000, time: 2.5 },
|
||||
Rust: { rps: 280000, time: 2.6 },
|
||||
Go: { rps: 250000, time: 3.0 },
|
||||
Java: { rps: 200000, time: 3.5 },
|
||||
'Node.js': { rps: 220000, time: 3.2 },
|
||||
Python: { rps: 80000, time: 8.0 },
|
||||
Ruby: { rps: 70000, time: 9.0 },
|
||||
PHP: { rps: 90000, time: 7.5 }
|
||||
},
|
||||
compute: {
|
||||
'C++': { rps: 500000, time: 1.5 },
|
||||
Rust: { rps: 480000, time: 1.6 },
|
||||
Go: { rps: 400000, time: 2.0 },
|
||||
Java: { rps: 350000, time: 2.3 },
|
||||
'Node.js': { rps: 50000, time: 15.0 },
|
||||
Python: { rps: 30000, time: 25.0 },
|
||||
Ruby: { rps: 25000, time: 30.0 },
|
||||
PHP: { rps: 35000, time: 20.0 }
|
||||
}
|
||||
hello: [
|
||||
{ language: 'C++', rps: 1500000 },
|
||||
{ language: 'Rust', rps: 1200000 },
|
||||
{ language: 'Go', rps: 1000000 },
|
||||
{ language: 'Node.js', rps: 800000 },
|
||||
{ language: 'Java', rps: 700000 },
|
||||
{ language: 'Python', rps: 200000 },
|
||||
{ language: 'Ruby', rps: 150000 }
|
||||
],
|
||||
json: [
|
||||
{ language: 'C++', rps: 800000 },
|
||||
{ language: 'Rust', rps: 700000 },
|
||||
{ language: 'Go', rps: 600000 },
|
||||
{ language: 'Node.js', rps: 450000 },
|
||||
{ language: 'Java', rps: 500000 },
|
||||
{ language: 'Python', rps: 150000 },
|
||||
{ language: 'Ruby', rps: 120000 }
|
||||
],
|
||||
db: [
|
||||
{ language: 'C++', rps: 300000 },
|
||||
{ language: 'Rust', rps: 280000 },
|
||||
{ language: 'Go', rps: 250000 },
|
||||
{ language: 'Node.js', rps: 220000 },
|
||||
{ language: 'Java', rps: 200000 },
|
||||
{ language: 'Python', rps: 80000 },
|
||||
{ language: 'Ruby', rps: 70000 }
|
||||
],
|
||||
compute: [
|
||||
{ language: 'C++', rps: 500000 },
|
||||
{ language: 'Rust', rps: 480000 },
|
||||
{ language: 'Go', rps: 400000 },
|
||||
{ language: 'Java', rps: 350000 },
|
||||
{ language: 'Node.js', rps: 50000 },
|
||||
{ language: 'Python', rps: 30000 },
|
||||
{ language: 'Ruby', rps: 25000 }
|
||||
]
|
||||
}
|
||||
|
||||
const explanations = {
|
||||
hello:
|
||||
'简单的 Hello World HTTP 响应测试。C++ 和 Rust 在这个测试中展现出接近硬件的性能优势。Go 和 Node.js 表现也很优秀,因为它们的 HTTP 栈经过高度优化。Python 和 Ruby 由于解释器开销,性能相对较低。',
|
||||
json: 'JSON 序列化/反序列化测试。这个测试考验语言的 JSON 处理能力。C++ 和 Rust 依然领先,但 Node.js 的表现也不错(V8 引擎优化)。Python 的标准库 json 模块性能尚可,但比编译型语言慢很多。',
|
||||
db: '模拟数据库查询(连接池 + 查询)。这个测试更接近真实应用。性能差距缩小了,因为瓶颈主要在数据库 I/O 而非语言本身。但依然能看到编译型语言(C++、Rust、Go)的优势。',
|
||||
compute:
|
||||
'CPU 密集型计算(斐波那契数列)。这个测试充分暴露了 Node.js 的短板:单线程 + V8 编译优化不如静态语言。Python 和 Ruby 表现最差,因为它们是解释型语言,且 GIL 限制了多线程性能。C++ 和 Rust 几乎是唯一选择。'
|
||||
hello: '简单的 HTTP 响应测试。C++ 和 Rust 展现出接近硬件的性能优势。Go 和 Node.js 表现优秀(HTTP 栈经过高度优化)。Python 和 Ruby 由于解释器开销,性能相对较低。',
|
||||
json: 'JSON 序列化测试。C++ 和 Rust 依然领先,Node.js 的 V8 引擎优化让它的表现也不错。Python 标准库 json 模块性能尚可,但比编译型语言慢很多。',
|
||||
db: '模拟数据库查询。性能差距缩小,因为瓶颈主要在数据库 I/O。但编译型语言(C++、Rust、Go)的优势依然明显。',
|
||||
compute: 'CPU 密集型计算(斐波那契)。Node.js 的短板暴露:单线程 + V8 优化不如静态语言。Python 和 Ruby 表现最差(解释型语言 + GIL 限制)。'
|
||||
}
|
||||
|
||||
const currentResults = ref([])
|
||||
@@ -129,16 +124,10 @@ const runBenchmark = () => {
|
||||
isRunning.value = true
|
||||
currentResults.value = []
|
||||
|
||||
// 模拟测试延迟
|
||||
setTimeout(() => {
|
||||
const data = benchmarkData[selectedScenario.value]
|
||||
currentResults.value = Object.entries(data).map(([language, stats]) => ({
|
||||
language,
|
||||
rps: stats.rps,
|
||||
time: stats.time
|
||||
}))
|
||||
currentResults.value = benchmarkData[selectedScenario.value]
|
||||
isRunning.value = false
|
||||
}, 1000)
|
||||
}, 800)
|
||||
}
|
||||
|
||||
const getBarWidth = (rps) => {
|
||||
@@ -147,8 +136,8 @@ const getBarWidth = (rps) => {
|
||||
}
|
||||
|
||||
const getBarClass = (rps) => {
|
||||
if (rps >= 800000) return 'bar-high'
|
||||
if (rps >= 300000) return 'bar-medium'
|
||||
if (rps >= 500000) return 'bar-high'
|
||||
if (rps >= 200000) return 'bar-medium'
|
||||
return 'bar-low'
|
||||
}
|
||||
|
||||
@@ -162,69 +151,106 @@ const getCurrentExplanation = () => {
|
||||
return explanations[selectedScenario.value]
|
||||
}
|
||||
|
||||
// 初始运行一次
|
||||
runBenchmark()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.performance-benchmark-demo {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.benchmark-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.benchmark-controls h4 {
|
||||
margin: 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
.demo-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.scenario-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scenario-selector label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.control-group select {
|
||||
padding: 0.5rem 1rem;
|
||||
.scenario-selector select {
|
||||
padding: 0.4rem 0.75rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-bg);
|
||||
background: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.run-btn {
|
||||
padding: 0.5rem 1.5rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.run-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
|
||||
background: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.run-btn:disabled {
|
||||
@@ -233,68 +259,47 @@ runBenchmark()
|
||||
}
|
||||
|
||||
.results-panel {
|
||||
margin-bottom: 2rem;
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.results-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
.panel-header {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.results-header h5 {
|
||||
margin: 0;
|
||||
.panel-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
.bars-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.chart-bar-wrapper {
|
||||
.bar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
min-width: 80px;
|
||||
min-width: 70px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.bar-container {
|
||||
.bar-track {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
height: 24px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
@@ -302,87 +307,35 @@ runBenchmark()
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 1rem;
|
||||
transition: width 0.8s ease-out;
|
||||
padding-right: 0.5rem;
|
||||
transition: width 0.5s ease;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.bar-high {
|
||||
background: linear-gradient(90deg, #f59e0b, #d97706);
|
||||
background: var(--vp-c-green-1);
|
||||
}
|
||||
|
||||
.bar-medium {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
background: var(--vp-c-yellow-1);
|
||||
}
|
||||
|
||||
.bar-low {
|
||||
background: linear-gradient(90deg, #6366f1, #4f46e5);
|
||||
background: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.bar-enter-active,
|
||||
.bar-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.bar-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.bar-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
.explanation-panel {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.explanation-panel h5 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.explanation-content p {
|
||||
margin: 0;
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.benchmark-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.run-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.results-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
min-width: 60px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
+126
-294
@@ -1,80 +1,58 @@
|
||||
<template>
|
||||
<div class="syntax-comparison-demo">
|
||||
<div class="demo-header">
|
||||
<h4>Hello World 语法对比</h4>
|
||||
<p class="subtitle">同样的功能,不同的实现方式</p>
|
||||
<span class="icon">📝</span>
|
||||
<span class="title">语法对比镜</span>
|
||||
<span class="subtitle">同样的功能,不同的表达方式</span>
|
||||
</div>
|
||||
|
||||
<div class="language-selector">
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">写信</span>:有人喜欢简洁明了(Python、Ruby),有人喜欢正式严谨(Java、C#),有人喜欢直接高效(Go)。不同语言的语法反映了不同的设计哲学。
|
||||
</div>
|
||||
|
||||
<div class="language-tabs">
|
||||
<button
|
||||
v-for="lang in languages"
|
||||
:key="lang.name"
|
||||
class="lang-btn"
|
||||
class="lang-tab"
|
||||
:class="{ active: selectedLang === lang.name }"
|
||||
@click="selectedLang = lang.name"
|
||||
>
|
||||
<span class="lang-icon">{{ lang.icon }}</span>
|
||||
<span class="lang-name">{{ lang.name }}</span>
|
||||
<span class="tab-icon">{{ lang.icon }}</span>
|
||||
<span class="tab-name">{{ lang.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<transition name="fade" mode="out-in">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div :key="selectedLang" class="code-display">
|
||||
<div class="code-header">
|
||||
<div class="window-controls">
|
||||
<span class="control red"></span>
|
||||
<span class="control yellow"></span>
|
||||
<span class="control green"></span>
|
||||
<div class="code-window">
|
||||
<div class="window-header">
|
||||
<div class="window-controls">
|
||||
<span class="control red"></span>
|
||||
<span class="control yellow"></span>
|
||||
<span class="control green"></span>
|
||||
</div>
|
||||
<div class="file-name">{{ getCode(selectedLang).filename }}</div>
|
||||
</div>
|
||||
<div class="filename">{{ getFileName(selectedLang) }}</div>
|
||||
<pre class="code-content">{{ getCode(selectedLang).code }}</pre>
|
||||
</div>
|
||||
|
||||
<pre class="code-content"><code>{{ getCode(selectedLang) }}</code></pre>
|
||||
|
||||
<div class="code-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">代码行数:</span>
|
||||
<span class="stat-value">{{ getLineCount(selectedLang) }} 行</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">字符数:</span>
|
||||
<span class="stat-value"
|
||||
>{{ getCharCount(selectedLang) }} 字符</span
|
||||
>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">复杂度:</span>
|
||||
<span class="stat-value">{{ getComplexity(selectedLang) }}</span>
|
||||
<span class="stat-value">{{ getCode(selectedLang).complexity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
|
||||
<div class="analysis-panel">
|
||||
<h5>💡 语法分析</h5>
|
||||
<div class="analysis-content">
|
||||
<p>{{ getAnalysis(selectedLang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="complexity-chart">
|
||||
<h5>代码复杂度对比</h5>
|
||||
<div class="chart-bars">
|
||||
<div
|
||||
v-for="lang in languages"
|
||||
:key="lang.name"
|
||||
class="chart-bar-wrapper"
|
||||
>
|
||||
<div class="chart-label">{{ lang.name }}</div>
|
||||
<div class="chart-bar">
|
||||
<div
|
||||
class="chart-fill"
|
||||
:style="{ width: getComplexityWidth(lang.name) + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="chart-value">{{ getLineCount(lang.name) }} 行</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>核心思想:</strong>简洁的语法(Python、Ruby)让开发更快,但冗长的语法(Java、C#)提供了更强的类型安全性和可维护性。没有"最好"的语法,只有最适合团队的语法。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -86,13 +64,10 @@ const selectedLang = ref('Python')
|
||||
|
||||
const languages = [
|
||||
{ name: 'Python', icon: '🐍' },
|
||||
{ name: 'Ruby', icon: '💎' },
|
||||
{ name: 'Go', icon: '🐹' },
|
||||
{ name: 'Node.js', icon: '💚' },
|
||||
{ name: 'Java', icon: '☕' },
|
||||
{ name: 'C#', icon: '💜' },
|
||||
{ name: 'Rust', icon: '🦀' },
|
||||
{ name: 'C++', icon: '⚡' }
|
||||
{ name: 'Rust', icon: '🦀' }
|
||||
]
|
||||
|
||||
const codes = {
|
||||
@@ -101,11 +76,6 @@ const codes = {
|
||||
filename: 'hello.py',
|
||||
complexity: '极简'
|
||||
},
|
||||
Ruby: {
|
||||
code: `puts "Hello, World!"`,
|
||||
filename: 'hello.rb',
|
||||
complexity: '极简'
|
||||
},
|
||||
Go: {
|
||||
code: `package main
|
||||
|
||||
@@ -131,175 +101,136 @@ func main() {
|
||||
filename: 'HelloWorld.java',
|
||||
complexity: '冗长'
|
||||
},
|
||||
'C#': {
|
||||
code: `using System;
|
||||
|
||||
class Program {
|
||||
static void Main() {
|
||||
Console.WriteLine("Hello, World!");
|
||||
}
|
||||
}`,
|
||||
filename: 'Program.cs',
|
||||
complexity: '冗长'
|
||||
},
|
||||
Rust: {
|
||||
code: `fn main() {
|
||||
println!("Hello, World!");
|
||||
}`,
|
||||
filename: 'main.rs',
|
||||
complexity: '简洁'
|
||||
},
|
||||
'C++': {
|
||||
code: `#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
return 0;
|
||||
}`,
|
||||
filename: 'hello.cpp',
|
||||
complexity: '中等'
|
||||
}
|
||||
}
|
||||
|
||||
const analyses = {
|
||||
Python:
|
||||
'Python 的语法极其简洁,只有一行代码。这也是为什么它被称为"伪代码语言"——读起来就像英语一样自然。没有任何样板代码,直接表达意图。',
|
||||
Ruby: 'Ruby 受 Perl 影响,语法非常优雅。puts 是 "put string" 的缩写,字符串不需要括号(虽然可以加)。Ruby 哲学是"程序员快乐至上"。',
|
||||
Go: 'Go 的语法虽然比 Python 冗长,但非常清晰。package main、import、func main() 都是必要的显式声明,这让代码更容易理解和维护。',
|
||||
'Node.js':
|
||||
'Node.js 使用 JavaScript,语法简单直接。console.log() 是浏览器和 Node.js 通用的输出方式。前端开发者零学习成本。',
|
||||
Java: 'Java 是典型的"仪式感"语言。class、public static void main、String[] args 都是必须的样板代码。虽然冗长,但结构清晰,适合大型项目。',
|
||||
'C#': 'C# 和 Java 非常相似,同样需要 class 和 Main 方法。using System 类似 Java 的 import,但更现代一些。.NET Core 后跨平台能力大幅提升。',
|
||||
Rust: 'Rust 的 fn main() 和 println! 宏看起来简洁,但 println! 后面的 ! 表示这是一个宏(不是函数)。Rust 的简洁来自于零成本抽象的设计哲学。',
|
||||
'C++':
|
||||
'C++ 需要 #include 头文件,std::cout 使用流操作符 <<,return 0 表示程序成功退出。虽然比 C 语言简洁(printf),但依然保留了很多底层细节。'
|
||||
}
|
||||
|
||||
const getCode = (lang) => {
|
||||
return codes[lang].code
|
||||
}
|
||||
|
||||
const getFileName = (lang) => {
|
||||
return codes[lang].filename
|
||||
return codes[lang]
|
||||
}
|
||||
|
||||
const getLineCount = (lang) => {
|
||||
return codes[lang].code.split('\n').length
|
||||
}
|
||||
|
||||
const getCharCount = (lang) => {
|
||||
return codes[lang].code.replace(/\s/g, '').length
|
||||
}
|
||||
|
||||
const getComplexity = (lang) => {
|
||||
return codes[lang].complexity
|
||||
}
|
||||
|
||||
const getAnalysis = (lang) => {
|
||||
return analyses[lang]
|
||||
}
|
||||
|
||||
const getComplexityWidth = (lang) => {
|
||||
const max = 10 // Java is the longest
|
||||
const lines = getLineCount(lang)
|
||||
return (lines / max) * 100
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.syntax-comparison-demo {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.language-selector {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 2rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lang-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.demo-header .icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.demo-header .title {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.demo-header .subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.language-tabs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.lang-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.lang-btn:hover {
|
||||
.lang-tab:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.lang-btn.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
.lang-tab.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.lang-icon {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lang-name {
|
||||
font-size: 0.95rem;
|
||||
.tab-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.code-display {
|
||||
background: #1e1e1e;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
.code-window {
|
||||
background: #1e1e1e;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.8rem 1rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #2d2d2d;
|
||||
border-bottom: 1px solid #3e3e3e;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.control {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@@ -315,131 +246,47 @@ const getComplexityWidth = (lang) => {
|
||||
background: #27c93f;
|
||||
}
|
||||
|
||||
.filename {
|
||||
.file-name {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #858585;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.code-content {
|
||||
margin: 0;
|
||||
padding: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-content code {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.code-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 1rem 1.5rem;
|
||||
background: #2d2d2d;
|
||||
border-top: 1px solid #3e3e3e;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #858585;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: #4ec9b0;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 600;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.analysis-panel {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border-left: 4px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.analysis-panel h5 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--vp-c-brand);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.analysis-content p {
|
||||
margin: 0;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.complexity-chart {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.complexity-chart h5 {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.chart-bar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.chart-label {
|
||||
min-width: 80px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
height: 30px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand), #8b5cf6);
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.chart-value {
|
||||
min-width: 60px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.85rem;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
@@ -447,30 +294,15 @@ const getComplexityWidth = (lang) => {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.language-selector {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.lang-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.code-stats {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.chart-bar-wrapper {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.chart-label {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.chart-value {
|
||||
min-width: 50px;
|
||||
}
|
||||
.info-box .icon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user