Files
tyapi-frontend/src/components/common/CodeDisplay.vue
2025-11-24 16:06:44 +08:00

400 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="code-display h-full flex flex-col">
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center p-8 flex-1">
<el-skeleton :rows="10" animated />
</div>
<!-- 语言选择标签页 -->
<div v-else class="flex flex-col h-full">
<div class="flex-shrink-0 mb-4">
<el-tabs
v-model="activeLanguage"
type="card"
@tab-click="handleTabClick"
class="custom-tabs"
>
<el-tab-pane
v-for="lang in languages"
:key="lang.key"
:label="lang.label"
:name="lang.key"
>
<template #label>
<div class="flex items-center gap-2 px-1">
<span class="text-sm font-medium">{{ lang.label }}</span>
<span class="text-xs text-gray-400 font-mono">{{ lang.extension }}</span>
</div>
</template>
</el-tab-pane>
</el-tabs>
</div>
<!-- 代码内容区域 - 可滚动 -->
<div class="flex-1 min-h-0 flex flex-col">
<div class="flex justify-between items-center mb-3 flex-shrink-0">
<h4 class="text-sm font-semibold text-gray-800 m-0">
{{ languages.find(l => l.key === activeLanguage)?.label }} 示例代码
</h4>
<div class="flex gap-2">
<el-button
type="primary"
size="small"
@click="copyCode(activeLanguage)"
class="text-xs"
>
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
复制代码
</el-button>
<el-button
type="success"
size="small"
@click="downloadCurrentCode"
:disabled="!hasExampleCodes"
class="text-xs"
>
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
下载代码
</el-button>
</div>
</div>
<!-- 代码内容 - 可滚动 -->
<div class="flex-1 bg-gray-900 rounded-lg overflow-hidden">
<div class="h-full overflow-auto p-4">
<pre class="text-sm text-gray-100 m-0 font-mono leading-relaxed whitespace-pre-wrap"><code>{{ getCodeContent(activeLanguage) }}</code></pre>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { computed, onMounted, ref } from 'vue'
// Props
const props = defineProps({
// 移除 productCode因为这是通用的API示例代码
})
// 响应式数据
const activeLanguage = ref('go')
const exampleCodes = ref({})
const loading = ref(false)
// 支持的语言列表
const languages = [
{ key: 'go', label: 'Go', extension: '.go', filename: 'demo.go' },
{ key: 'java', label: 'Java', extension: '.java', filename: 'demo.java' },
{ key: 'nodejs', label: 'Node.js', extension: '.js', filename: 'demo.js' },
{ key: 'csharp', label: 'C#', extension: '.cs', filename: 'demo.cs' },
{ key: 'python', label: 'Python', extension: '.py', filename: 'demo.py' },
{ key: 'php', label: 'PHP', extension: '.php', filename: 'demo.php' }
]
// 计算属性
const hasExampleCodes = computed(() => {
return Object.keys(exampleCodes.value).length > 0
})
// 生命周期
onMounted(() => {
loadExampleCodes()
})
// 方法
const handleTabClick = (tab) => {
activeLanguage.value = tab.name
}
const loadExampleCodes = async () => {
loading.value = true
try {
// 并行加载所有语言的示例代码
const promises = languages.map(async (lang) => {
try {
const code = await loadCodeFile(lang.key, lang.filename)
return { key: lang.key, code }
} catch (error) {
console.warn(`加载${lang.label}示例代码失败:`, error)
return { key: lang.key, code: null }
}
})
const results = await Promise.all(promises)
// 构建示例代码对象
results.forEach(result => {
if (result.code) {
exampleCodes.value[result.key] = result.code
}
})
} catch (error) {
console.error('加载示例代码失败:', error)
ElMessage.error('加载示例代码失败')
} finally {
loading.value = false
}
}
const loadCodeFile = async (langKey, filename) => {
try {
// 从 public 目录加载示例代码文件
const response = await fetch(`/examples/${langKey}/${filename}`)
if (response.ok) {
return await response.text()
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
} catch (error) {
console.warn(`加载${filename}失败:`, error)
// 如果加载失败,返回默认的示例代码
return getDefaultExampleCode(langKey)
}
}
const getDefaultExampleCode = (langKey) => {
const defaultCodes = {
go: `package main
import (
"fmt"
"net/http"
)
func main() {
fmt.Println("Go示例代码加载中...")
// 请检查源码文件是否存在
}`,
java: `public class ApiClient {
public static void main(String[] args) {
System.out.println("Java示例代码加载中...");
// 请检查源码文件是否存在
}
}`,
nodejs: `const ApiClient = require('./api-client');
console.log('Node.js示例代码加载中...');
// 请检查源码文件是否存在`,
csharp: `using System;
public class ApiClient {
public static void Main(string[] args) {
Console.WriteLine("C#示例代码加载中...");
// 请检查源码文件是否存在
}
}`,
python: `#!/usr/bin/env python3
class ApiClient:
def __init__(self):
print("Python示例代码加载中...")
# 请检查源码文件是否存在
if __name__ == "__main__":
client = ApiClient()`,
php: `<?php
class ApiClient {
public function __construct() {
echo "PHP示例代码加载中...\n";
// 请检查源码文件是否存在
}
}
$client = new ApiClient();`
}
return defaultCodes[langKey] || '// 示例代码加载中...'
}
const getCodeContent = (langKey) => {
const code = exampleCodes.value[langKey]
if (!code) {
return `// 暂无 ${languages.find(l => l.key === langKey)?.label} 示例代码`
}
// 过滤掉 source map 注释行
return code.split('\n')
.filter(line => !line.trim().startsWith('//# sourceMappingURL='))
.join('\n')
}
const copyCode = async (langKey) => {
const code = getCodeContent(langKey)
try {
await navigator.clipboard.writeText(code)
ElMessage.success(`${languages.find(l => l.key === langKey)?.label} 代码已复制到剪贴板`)
} catch (error) {
console.error('复制失败:', error)
ElMessage.error('复制失败,请手动复制')
}
}
const downloadCurrentCode = () => {
if (!hasExampleCodes.value) {
ElMessage.warning('暂无示例代码可下载')
return
}
const lang = languages.find(l => l.key === activeLanguage.value)
if (!lang) {
ElMessage.error('未找到当前语言的示例代码')
return
}
const code = getCodeContent(activeLanguage.value)
if (code && !code.includes('暂无') && !code.includes('加载中')) {
// 过滤掉 source map 注释行,确保下载的文件也不包含
const cleanCode = code.split('\n')
.filter(line => !line.trim().startsWith('//# sourceMappingURL='))
.join('\n')
const blob = new Blob([cleanCode], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `天远数据API_Demo_${lang.label}${lang.extension}`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
ElMessage.success(`${lang.label} 代码已下载`)
} else {
ElMessage.warning('当前语言暂无示例代码或加载失败')
}
}
</script>
<style scoped>
.code-display {
min-height: 400px;
}
/* 自定义标签页样式 */
:deep(.custom-tabs) {
--el-tabs-header-height: 48px;
}
:deep(.custom-tabs .el-tabs__header) {
margin-bottom: 0;
border-bottom: 1px solid #e5e7eb;
background: #f9fafb;
border-radius: 8px 8px 0 0;
padding: 0 16px;
}
:deep(.custom-tabs .el-tabs__nav-wrap) {
padding: 0;
}
:deep(.custom-tabs .el-tabs__nav) {
border: none;
background: transparent;
}
:deep(.custom-tabs .el-tabs__item) {
height: 48px;
line-height: 48px;
font-size: 14px;
font-weight: 500;
color: #6b7280;
background: transparent;
border: none;
border-radius: 6px 6px 0 0;
margin-right: 4px;
padding: 0 16px;
transition: all 0.2s ease;
position: relative;
}
:deep(.custom-tabs .el-tabs__item:hover) {
color: #3b82f6;
background: rgba(59, 130, 246, 0.05);
}
:deep(.custom-tabs .el-tabs__item.is-active) {
color: #3b82f6;
background: white;
border-bottom: 2px solid #3b82f6;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
:deep(.custom-tabs .el-tabs__item.is-active::before) {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: #3b82f6;
border-radius: 6px 6px 0 0;
}
:deep(.custom-tabs .el-tabs__content) {
padding: 0;
background: white;
border-radius: 0 0 8px 8px;
border: 1px solid #e5e7eb;
border-top: none;
}
:deep(.custom-tabs .el-tabs__active-bar) {
display: none;
}
/* 代码区域样式 */
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
/* 滚动条样式 */
.overflow-auto::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.overflow-auto::-webkit-scrollbar-track {
background: #374151;
border-radius: 4px;
}
.overflow-auto::-webkit-scrollbar-thumb {
background: #6b7280;
border-radius: 4px;
}
.overflow-auto::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
/* 标签页内容区域 */
.overflow-auto::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.overflow-auto::-webkit-scrollbar-track {
background: #374151;
border-radius: 4px;
}
.overflow-auto::-webkit-scrollbar-thumb {
background: #6b7280;
border-radius: 4px;
}
.overflow-auto::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
</style>