feat: CodePlay 第二阶段优化 - 转换质量与特性完善
核心修复: - 修复 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 <monkeycode-ai@chaitin.com>
This commit is contained in:
+42
-124
@@ -1,7 +1,30 @@
|
||||
<template>
|
||||
<el-config-provider :locale="zhCn" :namespace="theme">
|
||||
<div class="app-container" :class="{ 'dark-mode': isDark }">
|
||||
<router-view />
|
||||
<el-config-provider :locale="zhCn">
|
||||
<div class="app" :class="{ 'dark-mode': isDark }">
|
||||
<!-- 导航栏 -->
|
||||
<el-menu mode="horizontal" :router="true" class="nav-menu">
|
||||
<el-menu-item index="/converter">
|
||||
<el-icon><Cpu /></el-icon>
|
||||
<span>代码转换</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/projects">
|
||||
<el-icon><Folder /></el-icon>
|
||||
<span>项目</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/reports">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>报告</span>
|
||||
</el-menu-item>
|
||||
|
||||
<div class="menu-right">
|
||||
<el-button :icon="isDark ? Sunny : Moon" circle @click="toggleTheme" />
|
||||
</div>
|
||||
</el-menu>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<main class="main-content">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
@@ -9,143 +32,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import { Cpu, Folder, Document, Sunny, Moon } from '@element-plus/icons-vue'
|
||||
|
||||
const theme = ref('el-theme')
|
||||
const isDark = ref(false)
|
||||
|
||||
// 从 localStorage 加载主题
|
||||
onMounted(() => {
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
if (savedTheme === 'dark') {
|
||||
isDark.value = true
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
const saved = localStorage.getItem('theme')
|
||||
if (saved === 'dark') { isDark.value = true; document.documentElement.classList.add('dark') }
|
||||
})
|
||||
|
||||
// 监听主题变化
|
||||
watch(isDark, (val) => {
|
||||
if (val) {
|
||||
document.documentElement.classList.add('dark')
|
||||
localStorage.setItem('theme', 'dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
localStorage.setItem('theme', 'light')
|
||||
}
|
||||
watch(isDark, val => {
|
||||
if (val) { document.documentElement.classList.add('dark'); localStorage.setItem('theme', 'dark') }
|
||||
else { document.documentElement.classList.remove('dark'); localStorage.setItem('theme', 'light') }
|
||||
})
|
||||
|
||||
const toggleTheme = () => { isDark.value = !isDark.value }
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box }
|
||||
html, body, #app { width: 100%; height: 100% }
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.app { width: 100%; height: 100%; display: flex; flex-direction: column }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
.nav-menu { border-bottom: 1px solid #e0e0e0; padding: 0 20px; }
|
||||
.nav-menu .menu-right { float: right; margin-top: 10px }
|
||||
.dark .nav-menu { border-bottom-color: #434343; background: #1d1d1d; }
|
||||
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.main-content { flex: 1; overflow: hidden; }
|
||||
|
||||
/* 暗黑模式全局样式 */
|
||||
.dark {
|
||||
--el-bg-color: #141414;
|
||||
--el-bg-color-overlay: #1d1d1d;
|
||||
--el-text-color-primary: #e5e5e5;
|
||||
--el-text-color-regular: #d4d4d4;
|
||||
--el-text-color-secondary: #a8a8a8;
|
||||
--el-border-color: #434343;
|
||||
--el-fill-color: #262626;
|
||||
--el-color-primary: #409eff;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
background-color: #141414;
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.dark .el-header,
|
||||
.dark .el-footer,
|
||||
.dark .el-main {
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
.dark .el-card {
|
||||
background-color: #1d1d1d;
|
||||
border-color: #434343;
|
||||
}
|
||||
|
||||
.dark .el-table {
|
||||
--el-table-bg-color: #1d1d1d;
|
||||
--el-table-tr-bg-color: #1d1d1d;
|
||||
--el-table-header-bg-color: #262626;
|
||||
--el-table-text-color: #d4d4d4;
|
||||
--el-table-header-text-color: #e5e5e5;
|
||||
--el-table-border-color: #434343;
|
||||
}
|
||||
|
||||
.dark .el-input__wrapper {
|
||||
background-color: #1d1d1d;
|
||||
box-shadow: 0 0 0 1px #434343 inset;
|
||||
}
|
||||
|
||||
.dark .el-input__inner {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.dark .el-select-dropdown {
|
||||
background-color: #1d1d1d;
|
||||
}
|
||||
|
||||
.dark .el-select-dropdown__item {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.dark .el-select-dropdown__item.selected {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.dark .el-select-dropdown__item.hover {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.dark .el-popper {
|
||||
background-color: #1d1d1d;
|
||||
border-color: #434343;
|
||||
}
|
||||
|
||||
.dark .el-message {
|
||||
--el-message-bg-color: #1d1d1d;
|
||||
--el-message-text-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.dark .el-notification {
|
||||
--el-notification-bg-color: #1d1d1d;
|
||||
--el-notification-text-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.dark .el-dialog {
|
||||
background-color: #1d1d1d;
|
||||
}
|
||||
|
||||
.dark .el-dialog__title {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.dark .el-dialog__body {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
.dark body { background: #141414; color: #e5e5e5 }
|
||||
</style>
|
||||
|
||||
@@ -84,7 +84,8 @@ onMounted(() => {
|
||||
bracketPairColorization: { enabled: true },
|
||||
glyphMargin: true,
|
||||
folding: true,
|
||||
foldingStrategy: 'indentation'
|
||||
foldingStrategy: 'indentation',
|
||||
padding: { top: 10 }
|
||||
})
|
||||
|
||||
editor.onDidChangeModelContent(() => {
|
||||
@@ -223,12 +224,13 @@ defineExpose({
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.minimap {
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Converter from '@/views/Converter.vue'
|
||||
import ConverterView from '@/views/ConverterView.vue'
|
||||
import ProjectView from '@/views/ProjectView.vue'
|
||||
import ProjectsView from '@/views/ProjectsView.vue'
|
||||
import ReportsView from '@/views/ReportsView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
redirect: '/converter'
|
||||
},
|
||||
{
|
||||
path: '/converter',
|
||||
name: 'Converter',
|
||||
component: ConverterView
|
||||
},
|
||||
{
|
||||
path: '/projects',
|
||||
name: 'Projects',
|
||||
component: ProjectView
|
||||
}
|
||||
{ path: '/', redirect: '/converter' },
|
||||
{ path: '/converter', name: 'Converter', component: ConverterView },
|
||||
{ path: '/projects', name: 'Projects', component: ProjectsView },
|
||||
{ path: '/reports', name: 'Reports', component: ReportsView }
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -1,18 +1,164 @@
|
||||
const API_BASE = '/api'
|
||||
|
||||
export interface ConversionResult {
|
||||
success: boolean
|
||||
transformedCode: string
|
||||
errorMessage?: string
|
||||
report?: {
|
||||
id: string
|
||||
linesConverted: number
|
||||
classesConverted: number
|
||||
methodsConverted: number
|
||||
issueCount: number
|
||||
todoCount: number
|
||||
todoItems: TodoItem[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface TodoItem {
|
||||
lineNumber: number
|
||||
description: string
|
||||
recommendedAlternative: string
|
||||
}
|
||||
|
||||
export interface ProjectInfo {
|
||||
id: string
|
||||
name: string
|
||||
sourceLanguage: string
|
||||
targetLanguage: string
|
||||
createdAt: string
|
||||
updatedAt?: string
|
||||
files: string[]
|
||||
}
|
||||
|
||||
export interface ConversionReport {
|
||||
id: string
|
||||
projectId?: string
|
||||
sourceLanguage: string
|
||||
targetLanguage: string
|
||||
createdAt: string
|
||||
linesConverted: number
|
||||
classesConverted: number
|
||||
issueCount: number
|
||||
todoCount: number
|
||||
}
|
||||
|
||||
export const conversionApi = {
|
||||
async convert(sourceCode: string, sourceLanguage: string, targetLanguage: string, validationRounds: number = 2) {
|
||||
async convert(
|
||||
sourceCode: string,
|
||||
sourceLanguage: string,
|
||||
targetLanguage: string,
|
||||
validationRounds: number = 2
|
||||
): Promise<ConversionResult> {
|
||||
const response = await fetch(`${API_BASE}/conversion/convert`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
sourceCode,
|
||||
sourceLanguage,
|
||||
targetLanguage,
|
||||
validationRounds
|
||||
})
|
||||
body: JSON.stringify({ sourceCode, sourceLanguage, targetLanguage, validationRounds })
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
// 尝试解析 JSON 错误响应
|
||||
let errorMessage = '转换失败'
|
||||
try {
|
||||
const errorData = await response.json()
|
||||
errorMessage = errorData.message || errorData.error || errorData.detail || errorMessage
|
||||
} catch {
|
||||
// 如果不是 JSON,使用文本响应
|
||||
const text = await response.text()
|
||||
errorMessage = text || `${errorMessage} (HTTP ${response.status})`
|
||||
}
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
// 检查后端返回的错误
|
||||
if (!result.success && result.errorMessage) {
|
||||
throw new Error(result.errorMessage)
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
async batchConvert(
|
||||
sourceLanguage: string,
|
||||
targetLanguage: string,
|
||||
files: Array<{ fileName: string; content: string }>
|
||||
) {
|
||||
const response = await fetch(`${API_BASE}/conversion/batch`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sourceLanguage, targetLanguage, files })
|
||||
})
|
||||
if (!response.ok) throw new Error('批量转换失败')
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async getSupported() {
|
||||
const response = await fetch(`${API_BASE}/conversion/supported`)
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
// 项目管理
|
||||
async createProject(name: string, sourceLanguage: string, targetLanguage: string): Promise<ProjectInfo> {
|
||||
const response = await fetch(`${API_BASE}/Project`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, sourceLanguage, targetLanguage })
|
||||
})
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async getProjects(): Promise<ProjectInfo[]> {
|
||||
const response = await fetch(`${API_BASE}/Project`)
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async getProject(id: string): Promise<ProjectInfo> {
|
||||
const response = await fetch(`${API_BASE}/Project/${id}`)
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async addFileToProject(projectId: string, fileName: string) {
|
||||
const response = await fetch(`${API_BASE}/Project/${projectId}/files`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fileName })
|
||||
})
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async deleteProject(id: string) {
|
||||
await fetch(`${API_BASE}/Project/${id}`, { method: 'DELETE' })
|
||||
},
|
||||
|
||||
// 报告管理
|
||||
async getReports(limit: number = 50): Promise<ConversionReport[]> {
|
||||
const response = await fetch(`${API_BASE}/Report?limit=${limit}`)
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async getReportStats() {
|
||||
const response = await fetch(`${API_BASE}/Report/stats`)
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
// 文件上传
|
||||
async uploadFile(file: File): Promise<{ fileName: string; content: string; language: string }> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const response = await fetch(`${API_BASE}/File/upload`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
return await response.json()
|
||||
},
|
||||
|
||||
async uploadContent(fileName: string, content: string, language: string) {
|
||||
const response = await fetch(`${API_BASE}/File/upload-content`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fileName, content, language })
|
||||
})
|
||||
if (!response.ok) throw new Error('转换失败')
|
||||
return await response.json()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,18 +84,48 @@
|
||||
{{ conversionResult.report.classesConverted }} 个类
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: right;">
|
||||
<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 } from '@element-plus/icons-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'
|
||||
@@ -109,6 +139,7 @@ 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' }
|
||||
@@ -118,16 +149,52 @@ const getMonacoLanguage = (lang: string) => {
|
||||
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
|
||||
ElMessage.success(`转换成功:${result.report?.linesConverted || 0} 行`)
|
||||
} catch { ElMessage.error('转换失败') }
|
||||
finally { converting.value = false }
|
||||
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 () => {
|
||||
@@ -141,11 +208,18 @@ const copyResult = async () => {
|
||||
.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; }
|
||||
.editor-panel { padding: 0; display: flex; flex-direction: column; border-right: 1px solid #e0e0e0; }
|
||||
.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; }
|
||||
.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; }
|
||||
.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>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="projects-view">
|
||||
<el-container>
|
||||
<el-header class="toolbar">
|
||||
<h2>项目管理</h2>
|
||||
<el-button type="primary" @click="showCreateDialog = true" icon="Plus">新建项目</el-button>
|
||||
</el-header>
|
||||
|
||||
<el-main>
|
||||
<el-table :data="projects" stripe>
|
||||
<el-table-column prop="name" label="项目名称" />
|
||||
<el-table-column label="转换方向">
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ row.sourceLanguage }}</el-tag>
|
||||
<el-icon style="margin: 0 8px"><Right /></el-icon>
|
||||
<el-tag type="success">{{ row.targetLanguage }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="files" label="文件数" width="100">
|
||||
<template #default="{ row }">{{ row.files?.length || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ new Date(row.createdAt).toLocaleString() }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="viewProject(row)">查看</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteProject(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<!-- 创建项目对话框 -->
|
||||
<el-dialog v-model="showCreateDialog" title="创建项目" width="500px">
|
||||
<el-form :model="newProject" label-width="100px">
|
||||
<el-form-item label="项目名称">
|
||||
<el-input v-model="newProject.name" placeholder="输入项目名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="源语言">
|
||||
<el-select v-model="newProject.sourceLanguage">
|
||||
<el-option label="C#" value="CSharp" />
|
||||
<el-option label="Java" value="Java" />
|
||||
<el-option label="C++" value="CPlusPlus" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标语言">
|
||||
<el-select v-model="newProject.targetLanguage">
|
||||
<el-option label="Java" value="Java" />
|
||||
<el-option label="C#" value="CSharp" />
|
||||
<el-option label="C++" value="CPlusPlus" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showCreateDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="createProject">创建</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Right } from '@element-plus/icons-vue'
|
||||
import { conversionApi } from '../services/api'
|
||||
|
||||
const projects = ref<any[]>([])
|
||||
const showCreateDialog = ref(false)
|
||||
const newProject = ref({ name: '', sourceLanguage: 'CSharp', targetLanguage: 'Java' })
|
||||
|
||||
const loadProjects = async () => {
|
||||
try { projects.value = await conversionApi.getProjects() }
|
||||
catch { ElMessage.error('加载项目失败') }
|
||||
}
|
||||
|
||||
const createProject = async () => {
|
||||
try {
|
||||
await conversionApi.createProject(newProject.value.name, newProject.value.sourceLanguage, newProject.value.targetLanguage)
|
||||
ElMessage.success('项目创建成功')
|
||||
showCreateDialog.value = false
|
||||
loadProjects()
|
||||
} catch { ElMessage.error('创建失败') }
|
||||
}
|
||||
|
||||
const deleteProject = async (id: string) => {
|
||||
try {
|
||||
await conversionApi.deleteProject(id)
|
||||
ElMessage.success('删除成功')
|
||||
loadProjects()
|
||||
} catch { ElMessage.error('删除失败') }
|
||||
}
|
||||
|
||||
const viewProject = (project: any) => {
|
||||
ElMessage.info(`查看项目:${project.name}`)
|
||||
}
|
||||
|
||||
onMounted(() => { loadProjects() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.projects-view { width: 100%; height: 100%; }
|
||||
.toolbar { height: 60px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e0e0e0; }
|
||||
.dark .toolbar { border-bottom-color: #434343; background: #1d1d1d; }
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="reports-view">
|
||||
<el-container>
|
||||
<el-header class="toolbar">
|
||||
<h2>转换报告</h2>
|
||||
<el-button type="primary" @click="loadReports" icon="Refresh">刷新</el-button>
|
||||
</el-header>
|
||||
|
||||
<el-main>
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="20" style="margin-bottom: 20px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="总转换数" :value="stats.totalReports" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="总行数" :value="stats.totalLines" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="TODO 数" :value="stats.totalTODOs" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<el-statistic title="问题数" :value="stats.totalIssues" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 报告列表 -->
|
||||
<el-table :data="reports" stripe style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="150" />
|
||||
<el-table-column label="转换">
|
||||
<template #default="{ row }">
|
||||
{{ row.sourceLanguage }} → {{ row.targetLanguage }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="linesConverted" label="行数" width="100" />
|
||||
<el-table-column prop="classesConverted" label="类数" width="100" />
|
||||
<el-table-column prop="todoCount" label="TODO" width="80" />
|
||||
<el-table-column prop="issueCount" label="问题" width="80" />
|
||||
<el-table-column prop="createdAt" label="时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ new Date(row.createdAt).toLocaleString() }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { conversionApi } from '../services/api'
|
||||
|
||||
const reports = ref<any[]>([])
|
||||
const stats = ref({ totalReports: 0, totalLines: 0, totalTODOs: 0, totalIssues: 0 })
|
||||
|
||||
const loadReports = async () => {
|
||||
try {
|
||||
reports.value = await conversionApi.getReports()
|
||||
stats.value = await conversionApi.getReportStats()
|
||||
} catch {
|
||||
ElMessage.error('加载报告失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => { loadReports() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.reports-view { width: 100%; height: 100%; }
|
||||
.toolbar { height: 60px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e0e0e0; }
|
||||
.dark .toolbar { border-bottom-color: #434343; background: #1d1d1d; }
|
||||
</style>
|
||||
@@ -5,31 +5,17 @@ import path from 'path'
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
alias: { '@': path.resolve(__dirname, './src') }
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true,
|
||||
allowedHosts: ['.monkeycode-ai.online', 'localhost'],
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5000',
|
||||
target: 'http://localhost:5002',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['monaco-editor']
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'element-plus': ['element-plus'],
|
||||
'monaco-editor': ['monaco-editor']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user