Files
tyapi-frontend/src/components/common/CodeDisplay.vue

400 lines
10 KiB
Vue
Raw Normal View History

2025-11-24 16:06:44 +08:00
<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>