vue程序中使用Pyodide 详解

vue程序中使用Pyodide 详解


在这里插入图片描述

1. 完整的vue3组件代码

<template>
  <div class="pyodide-demo">
    <h2>🐍 Pyodide Python 代码执行器</h2>
    
    <div class="demo-container">
      <!-- 加载状态 -->
      <div v-if="isLoading" class="loading">
        <div class="spinner"></div>
        <p>正在加载 Pyodide... 请稍候</p>
      </div>

      <!-- 主界面 -->
      <div v-else class="main-interface">
        <!-- 代码编辑器 -->
        <div class="editor-section">
          <h3>Python 代码编辑器</h3>
          <textarea
            v-model="pythonCode"
            class="code-editor"
            placeholder="在这里输入 Python 代码..."
            @keydown.ctrl.enter="runCode"
            @keydown.meta.enter="runCode"
          ></textarea>
          
          <div class="editor-toolbar">
            <button @click="runCode" :disabled="isRunning" class="run-btn">
              {{ isRunning ? '运行中...' : '运行代码 (Ctrl+Enter)' }}
            </button>
            <button @click="clearOutput" class="clear-btn">清空输出</button>
            <button @click="loadExample" class="example-btn">加载示例</button>
          </div>
        </div>

        <!-- 输出区域 -->
        <div class="output-section">
          <h3>输出结果</h3>
          <div class="output-container">
            <pre v-if="output" class="output">{{ output }}</pre>
            <pre v-if="error" class="error">{{ error }}</pre>
            <div v-if="!output && !error" class="placeholder">
              运行 Python 代码后,结果将显示在这里
            </div>
          </div>
        </div>
      </div>

      <!-- 功能说明 -->
      <div class="features">
        <h3>✨ 功能特性</h3>
        <ul>
          <li>在浏览器中直接运行 Python 代码</li>
          <li>支持 NumPy、Pandas、Matplotlib 等科学计算库</li>
          <li>实时代码执行和结果显示</li>
          <li>错误处理和调试信息</li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

// 响应式数据
const isLoading = ref(true)
const isRunning = ref(false)
const pythonCode = ref('')
const output = ref('')
const error = ref('')

// Pyodide 实例类型定义
interface PyodideInterface {
  loadPackage: (packages: string[]) => Promise<unknown>
  runPython: (code: string) => unknown
}

let pyodide: PyodideInterface | null = null

// 初始化 Pyodide
const initPyodide = async () => {
  try {
    // 使用 CDN 加载 Pyodide,避免构建问题
    const script = document.createElement('script')
    script.src = 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js'
    document.head.appendChild(script)
    
    await new Promise((resolve) => {
      script.onload = resolve
    })
    
    // @ts-expect-error - 全局 loadPyodide 函数
    pyodide = await window.loadPyodide({
      indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/',
    })
    
    // 安装常用的科学计算包
    if (pyodide) {
      await pyodide.loadPackage(['numpy', 'matplotlib', 'pandas'])
    }
    
    console.log('Pyodide 加载完成')
    isLoading.value = false
  } catch (err) {
    console.error('Pyodide 加载失败:', err)
    error.value = `Pyodide 加载失败: ${err}`
    isLoading.value = false
  }
}

// 运行 Python 代码
const runCode = async () => {
  if (!pyodide || !pythonCode.value.trim()) {
    return
  }
  
  isRunning.value = true
  output.value = ''
  error.value = ''
  
  try {
    // 捕获 print 输出
    pyodide.runPython(`
import sys
from io import StringIO
sys.stdout = StringIO()
sys.stderr = StringIO()
    `)
    
    // 执行用户代码
    const result = pyodide.runPython(pythonCode.value)
    
    // 获取输出
    const stdout = pyodide.runPython('sys.stdout.getvalue()')
    const stderr = pyodide.runPython('sys.stderr.getvalue()')
    
    // 恢复标准输出
    pyodide.runPython(`
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
    `)
    
    // 组合输出结果
    let outputText = ''
    if (stdout) outputText += stdout
    if (result !== undefined && result !== null) {
      outputText += (outputText ? '\n' : '') + `返回值: ${result}`
    }
    if (stderr) {
      error.value = String(stderr)
    }
    
    output.value = outputText || '代码执行完成,无输出'
    
  } catch (err) {
    error.value = `执行错误: ${err}`
  } finally {
    isRunning.value = false
  }
}

// 清空输出
const clearOutput = () => {
  output.value = ''
  error.value = ''
}

// 加载示例代码
const loadExample = () => {
  pythonCode.value = `# Python 在浏览器中运行示例
import numpy as np
import matplotlib.pyplot as plt

# 创建数据
x = np.linspace(0, 2 * np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)

print("NumPy 数组操作:")
print(f"x 数组长度: {len(x)}")
print(f"sin(π/2) = {np.sin(np.pi/2):.2f}")
print(f"cos(π) = {np.cos(np.pi):.2f}")

# Pandas 示例
import pandas as pd
data = {
    'x': x[:10],
    'sin_x': y1[:10],
    'cos_x': y2[:10]
}
df = pd.DataFrame(data)
print("\\nPandas DataFrame (前5行):")
print(df.head())

# 计算统计信息
print(f"\\nsin(x) 的平均值: {df['sin_x'].mean():.3f}")
print(f"cos(x) 的标准差: {df['cos_x'].std():.3f}")
`
}

// 组件挂载时初始化
onMounted(() => {
  initPyodide()
  loadExample() // 默认加载示例代码
})
</script>

<style scoped>
.pyodide-demo {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

.demo-container {
  margin-top: 20px;
}

.loading {
  text-align: center;
  padding: 50px;
}

.spinner {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #42b883;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  animation: spin 1s linear infinite;
  margin: 0 auto 20px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.main-interface {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-bottom: 30px;
}

.editor-section,
.output-section {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  background: #f9f9f9;
}

.editor-section h3,
.output-section h3 {
  margin-top: 0;
  color: #2c3e50;
}

.code-editor {
  width: 100%;
  height: 300px;
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-family: 'Courier New', Monaco, monospace;
  font-size: 14px;
  resize: vertical;
  background: white;
}

.editor-toolbar {
  margin-top: 10px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.run-btn,
.clear-btn,
.example-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

.run-btn {
  background: #42b883;
  color: white;
}

.run-btn:hover:not(:disabled) {
  background: #369870;
}

.run-btn:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.clear-btn {
  background: #f56565;
  color: white;
}

.clear-btn:hover {
  background: #e53e3e;
}

.example-btn {
  background: #4299e1;
  color: white;
}

.example-btn:hover {
  background: #3182ce;
}

.output-container {
  height: 300px;
  overflow-y: auto;
  border: 1px solid #ccc;
  border-radius: 4px;
  background: white;
}

.output {
  padding: 15px;
  margin: 0;
  font-family: 'Courier New', Monaco, monospace;
  font-size: 14px;
  white-space: pre-wrap;
  color: #2c3e50;
}

.error {
  padding: 15px;
  margin: 0;
  font-family: 'Courier New', Monaco, monospace;
  font-size: 14px;
  white-space: pre-wrap;
  color: #e53e3e;
  background: #fed7d7;
}

.placeholder {
  padding: 15px;
  color: #999;
  font-style: italic;
  text-align: center;
}

.features {
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 8px;
  padding: 20px;
}

.features h3 {
  margin-top: 0;
  color: #0369a1;
}

.features ul {
  margin: 10px 0;
  padding-left: 20px;
}

.features li {
  margin: 8px 0;
  color: #374151;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .main-interface {
    grid-template-columns: 1fr;
  }
  
  .editor-toolbar {
    justify-content: center;
  }
}
</style>

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值