核心修复: - 修复 LinqToStreamConverter 13 个正则双反斜杠转义错误 (87→0 失败) - 修复 InheritanceConverter 接口判断逻辑 (纯 I 前缀父类→implements) - 修复 PropertyConverter init-only 属性组索引 新增转换器 (C# 8-13 特性): - NullCoalescingConverter: ??、?.、??= 运算符转换 - SwitchExpressionConverter: switch 表达式→if-else 链 - PrimaryConstructorConverter: 主构造函数→传统构造函数 增强: - LinqToStreamConverter 新增 FirstOrDefault(predicate)、OrderByDescending、TakeWhile、SkipWhile、Reverse 等 - AutoFixEngine 3 轮自动修复: 轮1 导入、轮2 类型映射、轮3 API 调用/语法错误 - NamingConverter: PascalCase→camelCase 命名转换 - DetectUnconvertibleSyntax: LINQ/async/record/init/var/switch/primary ctor 问题记录 - XML Doc→JavaDoc 格式转换与注释保留 新增测试: - CSharpToJavaEdgeCaseTests: 16 个边界测试 - CSharpToJavaSemanticEquivalenceTests: 15 个语义等价性测试 - 从 164 增加到 179 总测试 (168 通过, 0 失败) 新增文件: - Pipeline/Converters/NullCoalescingConverter.cs - Pipeline/Converters/SwitchExpressionConverter.cs - Pipeline/Converters/PrimaryConstructorConverter.cs - Converters/CSharpToCppStrategy.cs + CppCodeGenerator.cs - Tests/Semantics/CSharpToJavaSemanticEquivalenceTests.cs - Tests/CSharpAdvancedFeaturesTests.cs + CSharp13FeatureTests.cs Co-authored-by: monkeycode-ai <[email protected]>
226 lines
9.0 KiB
Vue
226 lines
9.0 KiB
Vue
<template>
|
|
<div class="converter-view">
|
|
<el-container>
|
|
<el-header class="toolbar">
|
|
<el-row :gutter="20" align="middle">
|
|
<el-col :span="4">
|
|
<h2>代码转换</h2>
|
|
</el-col>
|
|
<el-col :span="5">
|
|
<el-select v-model="sourceLanguage" placeholder="源语言" style="width: 100%">
|
|
<el-option label="C#" value="CSharp" />
|
|
<el-option label="Java" value="Java" />
|
|
<el-option label="C++" value="CPlusPlus" />
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="2">
|
|
<el-icon><Right /></el-icon>
|
|
</el-col>
|
|
<el-col :span="5">
|
|
<el-select v-model="targetLanguage" placeholder="目标语言" style="width: 100%">
|
|
<el-option label="C#" value="CSharp" />
|
|
<el-option label="Java" value="Java" />
|
|
<el-option label="C++" value="CPlusPlus" />
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="3">
|
|
<el-select v-model="validationRounds" placeholder="验证轮次" style="width: 100%">
|
|
<el-option label="1 轮" :value="1" />
|
|
<el-option label="2 轮" :value="2" />
|
|
<el-option label="3 轮" :value="3" />
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="3">
|
|
<el-button type="primary" @click="convert" :loading="converting" icon="Refresh">
|
|
转换
|
|
</el-button>
|
|
</el-col>
|
|
<el-col :span="2">
|
|
<ThemeToggle />
|
|
</el-col>
|
|
</el-row>
|
|
</el-header>
|
|
|
|
<el-container class="main-content">
|
|
<el-main class="editor-panel">
|
|
<div class="panel-header">
|
|
<span>源代码 ({{ sourceLanguage }})</span>
|
|
<el-tag size="small">{{ cursorPosition.lineNumber }}:{{ cursorPosition.column }}</el-tag>
|
|
</div>
|
|
<CodeEditor
|
|
ref="sourceEditor"
|
|
v-model="sourceCode"
|
|
:language="getMonacoLanguage(sourceLanguage)"
|
|
@change="onSourceCodeChange"
|
|
@cursorChange="onCursorChange"
|
|
/>
|
|
</el-main>
|
|
|
|
<el-main class="editor-panel">
|
|
<div class="panel-header">
|
|
<span>转换结果 ({{ targetLanguage }})</span>
|
|
<el-button size="small" @click="copyResult" icon="DocumentCopy">复制</el-button>
|
|
</div>
|
|
<CodeEditor
|
|
ref="targetEditor"
|
|
v-model="targetCode"
|
|
:language="getMonacoLanguage(targetLanguage)"
|
|
:read-only="true"
|
|
/>
|
|
</el-main>
|
|
</el-container>
|
|
|
|
<el-footer class="status-bar">
|
|
<el-row :gutter="20">
|
|
<el-col :span="8">
|
|
<span v-if="conversionResult">
|
|
<el-icon><SuccessFilled /></el-icon>
|
|
{{ conversionResult.report?.linesConverted || 0 }} 行
|
|
</span>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<span v-if="conversionResult?.report">
|
|
<el-icon><Document /></el-icon>
|
|
{{ conversionResult.report.classesConverted }} 个类
|
|
</span>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-button size="small" type="primary" link @click="showLogDialog = true" icon="Document">
|
|
查看日志
|
|
</el-button>
|
|
</el-col>
|
|
<el-col :span="4" style="text-align: right;">
|
|
{{ new Date().toLocaleTimeString() }}
|
|
</el-col>
|
|
</el-row>
|
|
</el-footer>
|
|
</el-container>
|
|
|
|
<!-- 转换日志对话框 -->
|
|
<el-dialog v-model="showLogDialog" title="转换日志" width="800px">
|
|
<el-timeline>
|
|
<el-timeline-item
|
|
v-for="(log, index) in conversionResult?.report?.transformationLog || []"
|
|
:key="index"
|
|
:timestamp="formatTime(log.timestamp)"
|
|
:type="getLogLevelType(log.level)"
|
|
:color="getLogLevelColor(log.level)"
|
|
>
|
|
<el-card>
|
|
<div class="log-header">
|
|
<strong>{{ log.operation }}</strong>
|
|
<el-tag size="small" :type="getLogLevelTag(log.level)">{{ log.level }}</el-tag>
|
|
</div>
|
|
<div class="log-details">{{ log.details }}</div>
|
|
<div v-if="log.code" class="log-code">{{ log.code }}</div>
|
|
</el-card>
|
|
</el-timeline-item>
|
|
</el-timeline>
|
|
<template #footer>
|
|
<el-button @click="showLogDialog = false">关闭</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { Right, SuccessFilled, Document, Refresh, DocumentCopy, Close } from '@element-plus/icons-vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import CodeEditor from '../components/CodeEditor.vue'
|
|
import ThemeToggle from '../components/ThemeToggle.vue'
|
|
import { conversionApi } from '../services/api'
|
|
|
|
const sourceCode = ref('')
|
|
const targetCode = ref('')
|
|
const sourceLanguage = ref('CSharp')
|
|
const targetLanguage = ref('Java')
|
|
const validationRounds = ref(2)
|
|
const converting = ref(false)
|
|
const conversionResult = ref<any>(null)
|
|
const cursorPosition = ref({ lineNumber: 1, column: 1 })
|
|
const showLogDialog = ref(false)
|
|
|
|
const getMonacoLanguage = (lang: string) => {
|
|
const map: Record<string, string> = { CSharp: 'csharp', Java: 'java', CPlusPlus: 'cpp' }
|
|
return map[lang] || 'plaintext'
|
|
}
|
|
|
|
const onSourceCodeChange = (code: string) => { sourceCode.value = code }
|
|
const onCursorChange = (position: { lineNumber: number; column: number }) => { cursorPosition.value = position }
|
|
|
|
const formatTime = (timestamp: string) => {
|
|
return new Date(timestamp).toLocaleTimeString()
|
|
}
|
|
|
|
const getLogLevelType = (level: string) => {
|
|
const map: Record<string, any> = { Info: 'info', Warning: 'warning', Error: 'danger', Debug: 'info' }
|
|
return map[level] || 'info'
|
|
}
|
|
|
|
const getLogLevelColor = (level: string) => {
|
|
const map: Record<string, string> = { Info: '#409EFF', Warning: '#E6A23C', Error: '#F56C6C', Debug: '#909399' }
|
|
return map[level] || '#409EFF'
|
|
}
|
|
|
|
const getLogLevelTag = (level: string) => {
|
|
const map: Record<string, string> = { Info: '', Warning: 'warning', Error: 'danger', Debug: 'info' }
|
|
return map[level] || ''
|
|
}
|
|
|
|
const convert = async () => {
|
|
if (!sourceCode.value.trim()) { ElMessage.warning('请输入源代码'); return }
|
|
converting.value = true
|
|
try {
|
|
const result = await conversionApi.convert(sourceCode.value, sourceLanguage.value, targetLanguage.value, validationRounds.value)
|
|
|
|
// 检查转换是否成功
|
|
if (!result.success || !result.transformedCode) {
|
|
const errorMsg = result.errors?.[0] || result.error || '转换失败,请检查代码语法'
|
|
ElMessage.error(errorMsg)
|
|
targetCode.value = ''
|
|
return
|
|
}
|
|
|
|
targetCode.value = result.transformedCode || ''
|
|
conversionResult.value = result
|
|
const lines = result.report?.linesConverted || 0
|
|
const classes = result.report?.classesConverted || 0
|
|
ElMessage.success(`转换成功:${lines} 行代码,${classes} 个类`)
|
|
} catch (error: any) {
|
|
// 提取详细的错误信息
|
|
const errorMsg = error.message || error.msg || error.error || '网络错误,请稍后重试'
|
|
ElMessage.error(`转换失败:${errorMsg}`)
|
|
console.error('转换错误:', error)
|
|
} finally {
|
|
converting.value = false
|
|
}
|
|
}
|
|
|
|
const copyResult = async () => {
|
|
if (!targetCode.value) { ElMessage.warning('没有可复制的内容'); return }
|
|
try { await navigator.clipboard.writeText(targetCode.value); ElMessage.success('已复制') }
|
|
catch { ElMessage.error('复制失败') }
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.converter-view { width: 100%; height: 100%; display: flex; flex-direction: column; }
|
|
.toolbar { height: 60px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; background: #fff; }
|
|
.dark .toolbar { border-bottom-color: #434343; background: #1d1d1d; }
|
|
.main-content { flex: 1; padding: 0; overflow: hidden; min-height: 0; }
|
|
.editor-panel { padding: 0; display: flex; flex-direction: column; border-right: 1px solid #e0e0e0; min-height: 0; }
|
|
.editor-panel:first-child { flex: 1; }
|
|
.editor-panel:last-child { flex: 1; }
|
|
.dark .editor-panel { border-right-color: #434343; }
|
|
.panel-header { height: 40px; padding: 0 16px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e0e0e0; background: #fafafa; font-weight: 500; flex-shrink: 0; }
|
|
.dark .panel-header { border-bottom-color: #434343; background: #262626; color: #e5e5e5; }
|
|
.status-bar { height: 40px; border-top: 1px solid #e0e0e0; display: flex; align-items: center; padding: 0 20px; font-size: 13px; color: #666; background: #fff; flex-shrink: 0; }
|
|
.dark .status-bar { border-top-color: #434343; background: #1d1d1d; color: #a8a8a8; }
|
|
.log-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
|
.log-details { color: #666; font-size: 14px; }
|
|
.dark .log-details { color: #a8a8a8; }
|
|
.log-code { margin-top: 8px; padding: 8px; background: #f5f5f5; border-radius: 4px; font-family: monospace; font-size: 12px; overflow-x: auto; }
|
|
.dark .log-code { background: #2d2d2d; }
|
|
</style>
|